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
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.