springvalidationthymeleafconditional-rendering

Thymeleaf error validation: spring boot starter validation always displaying errors


build.gradle file

    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
        implementation 'org.springframework.boot:spring-boot-starter-security'
        implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
        implementation 'org.springframework.boot:spring-boot-starter-web'
        implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
        runtimeOnly 'org.postgresql:postgresql'
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
        testImplementation 'org.springframework.security:spring-security-test'
        implementation 'io.hypersistence:hypersistence-utils-hibernate-60:3.1.1'
        // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation
        implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation', version: '3.0.1'
    }

UserModel file

    
    import jakarta.persistence.*;
    import jakarta.validation.constraints.NotEmpty;
    import jakarta.validation.constraints.Size;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.util.*;
    
    @Entity
    @Table(name = "users")
    public class UserModel implements UserDetails {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
    
        @NotEmpty()
        private String username;
    
        @NotEmpty(message = "Username not be Empty!")
        @Size(min = 6, max = 50)
        private String password;
    
        private List<String> authorities;
        private boolean isAccountNonExpired;
        private boolean isAccountNonLocked;
        private boolean isCredentialsNonExpired;
        private boolean isEnabled;
    
        public UserModel() {}
        public UserModel(String username, String password, List<String> authorities,boolean isAccountNonExpired, boolean isAccountNonLocked, boolean isCredentialsNonExpired, boolean isEnabled) {
            this.username = username;
            this.password = password;
            this.authorities = authorities;
    
            this.isAccountNonExpired = isAccountNonExpired;
            this.isAccountNonLocked = isAccountNonLocked;
            this.isCredentialsNonExpired = isCredentialsNonExpired;
            this.isEnabled = isEnabled;
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
    
            List<GrantedAuthority> authorityList = new ArrayList<>();
    
    
    
            return authorityList;
        }
    
        @Override
        public String getPassword() {
            return password;
        }
    
        @Override
        public String getUsername() {
            return username;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return isAccountNonExpired;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return isAccountNonLocked;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return isCredentialsNonExpired;
        }
    
        @Override
        public boolean isEnabled() {
            return isEnabled;
        }    
    }

Controller file


import com.example.demo.user.UserModel;
import com.example.demo.user.UserModelRepository;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class TestController {
    private final UserModelRepository userModelRepository;

    public TestController(UserModelRepository userModelRepository) {
        this.userModelRepository = userModelRepository;
    }

    @GetMapping("/register")
    public String showAddUserForm(UserModel userModel) {
        return "register";
    }

    @PostMapping("/register")
    public String addUser(@Valid UserModel userModel, BindingResult result, Model model) {

        if (result.hasErrors()) {
            return "register";
        }

        System.out.println(userModel);
        userModelRepository.save(userModel);

        model.addAttribute("users", userModelRepository.findAll());

        return "home";   // TODO - This will be executed inside of our HTML
    }

}

HTML file named: 'register.html'

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <title>Register</title>

    <style>
        .error {
            color: red;
        }
    </style>
</head>
<body>

    <!-- ${user} Object -->
    <!-- *{username} Object attribute -->

    <form th:action="@{/register}" th:object="${userModel}" method="post" class="form">
        <div>

            <input type="text" th:field="*{username}" id="username" placeholder="username" name="username">
            <!-- <p th:if="${#fields.hasErrors('username')}" th:errorclass="error" th:errors="*{username}" /> -->
            <p th:if="${#fields.hasErrors('username')}" th:errorclass="error" th:errors="*{username}"> </p>

        </div>
        <div>

            <h2> Password </h2>
            <input type="password" th:field="*{password}" >
            <ul>
                <li th:each="error : ${#fields.errors('password')}" th:text="${error}" class="error">
            </ul>

        </div>

        <div th:if="${#fields.hasAnyErrors()}">
            <ul>
                <li th:each="error : ${#fields.allErrors()}" th:text="${error}"></li>
            </ul>
        </div>

        <input type="submit" value="Add me">

    </form>

</body>
</html>

Problem Whenever i fill in my form it always displays errors whenever i click submit. Regardless if they're filled in or not. Why is it always displaying an error?

I'm guessing that somehow, although i'm trying to render a condition, it always turns to 'false'. I just can't see what i'm doing wrong.

Tutorial i'm following: https://www.baeldung.com/spring-thymeleaf-error-messages

Result


Solution

  • The problem is your UserModel it doesn't provide setter methods and thus nop data binding will (or even can) take place.

    To fix either add the setter methods or tell Spring to use direct field binding.

    By adding the following method to your controller you can achieve direct field binding/access.

    @InitBinder
    public void initBinder(WebDataBinder binder) {
      binder.initDirectFieldAccess();
    }
    

    The code above will use fields instead of properties (getter/setter pair) to do the binding. With that you can leave your UserModel unmodified and still have data binding applied.