Null fields in Thymeleaf form using Spring Boot, other fields fine - java

I'm relatively new to Spring Boot. Currently, I'm making a Spring Boot application with user registration system but I've run into an issue. Some of the fields in a form are registering as ‘null’ on the back end, despite the request being posted correctly.
I have a HTML/ Thymeleaf form which submits 8 fields to create a 'User' object. This is the form:
<form th:action="#{/users/register_attempt}" th:object="${user}"
method="post" style="max-width: 600px; margin: 0 auto;">
<div class="m-3">
<div class="form-group row">
<label class="col-4 col-form-label">DOB: </label>
<div class="col-8">
<input type="text" th:field="*{dob}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">First Name: </label>
<div class="col-8">
<input type="text" th:field="*{name}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">Surname: </label>
<div class="col-8">
<input type="text" th:field="*{surname}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">PPSN: </label>
<div class="col-8">
<input type="text" th:field="*{ppsn}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">Address: </label>
<div class="col-8">
<input type="text" th:field="*{address}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">Phone Number: </label>
<div class="col-8">
<input type="number" th:field="*{phone}" class="form-control" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">E-mail: </label>
<div class="col-8">
<input type="email" th:field="*{email}" class="form-control" th:required="required" />
</div>
</div>
<div class="form-group row">
<label class="col-4 col-form-label">Password: </label>
<div class="col-8">
<input type="password" th:field="*{password}" class="form-control"
required minlength="6" maxlength="10" th:required="required"/>
</div>
</div>
<div>
<button type="submit" class="btn btn-primary">Sign Up</button>
</div>
</div>
</form>
And here is the model that the form code is meant to mimic:
public class User {
#Id
#GeneratedValue
private Long id;
#NotBlank
private String dob;
#NotBlank
private String name;
#NotBlank
private String surname;
#NotBlank
private String ppsn;
#NotBlank
private String address;
#NotBlank
private String phone;
#NotBlank
#Column(unique = true)
private String email;
private String nextApptId;
private String dose1Date;
private String dose2Date;
private String lastLogin;
#NotBlank
private String password;
public User() {
super();
}
public User(String dob, String name, String surname, String ppsn, String address, String phone, String email, String password) {
super();
this.dob = dob;
this.name = name;
this.surname = surname;
this.ppsn = ppsn;
this.address = address;
this.phone = phone;
this.email = email;
this.password = password;
}
public Long getId() {
return id;
}
public String getDob() {
return dob;
}
public String getName() {
return name;
}
public String getSurname() {
return surname;
}
public String getPpsn() {
return ppsn;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getNextApptId() {
return nextApptId;
}
public void setNextApptId(String apptId) {
this.nextApptId = apptId;
}
public String getDose1Date() {
return dose1Date;
}
public void setDose1Date(String dose1Date) {
this.dose1Date = dose1Date;
}
public String getDose2Date() {
return dose2Date;
}
public void setDose2Date(String dose2Date) {
this.dose2Date = dose2Date;
}
public String getLastLogin() {
return lastLogin;
}
public void setLastLogin(String lastLogin) {
this.lastLogin = lastLogin;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
But for some reason I can't figure out, the first four fields - i.e. Dob (date of birth), Name, Surname, and PPSN, are producing a null error when instantiating the user object on the server side, e.g.:
`Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 4 errors<EOL>
Field error in object 'user' on field 'dob': rejected value [null]; codes [NotBlank.user.dob,NotBlank.dob,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.dob,dob]; arguments []; default message [dob]]; default message [must not be blank] `.
The other four fields, Address, Phone, Email, and Password appear to be working just fine.
As far as I can make out, there it isn't a typo issue (forgive me if that ends up being the case). I have intercepted the Post request using Burp to check that the contents of the fields were making it out of the form and into the request, with the right names for the fields in my User class, and indeed they are all there as intended.
I imagine this means that the issue is coming from how the back end controller code is interpreting this post request based on the model, but I have no real idea of how or where to start. Here is the current controller:
#GetMapping("/register")
public String startRegistration(Model model) {
model.addAttribute("user", new User());
return "register";
}
#PostMapping("/register_attempt")
public String registerAttempt(#Valid User newUser) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encodedPassword = passwordEncoder.encode(newUser.getPassword());
newUser.setPassword(encodedPassword);
userRepository.save(newUser);
return "registered_successfully";
}
EDIT: Further debugging & clarification with issue persisting
Removing the #Valid annotation from the Post Mapping and using old fashioned print statements shows the same results - that the first four fields are null, for no obvious reason.
#PostMapping("/register_attempt")
public String registerAttempt(#ModelAttribute("user") User newUser) {
System.out.println("Saving user ");
System.out.println("Name " + newUser.getName());
System.out.println("Last Name " + newUser.getSurname());
System.out.println("PPSN " +newUser.getPpsn());
System.out.println("DOB " +newUser.getDob());
System.out.println("email " +newUser.getEmail());
System.out.println("address " +newUser.getAddress());
System.out.println("encrypted password " +newUser.getPassword());
System.out.println("Phone num " +newUser.getPhone());
userRepository.save(newUser);
System.out.println("User saved");
return "registered_successfully";
}
Using the following dummy data:
Which sends this Post Request to the back end:
Results in these print statements:
Saving user
name null
last Name null
ppsn null
dob null
email foo#bar.com
address Here and there
password password
Phone num 987654321
And these error messages:
javax.validation.ConstraintViolationException: Validation failed for classes [app.model.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must not be blank', propertyPath=surname, rootBeanClass=class app.model.User, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
ConstraintViolationImpl{interpolatedMessage='must not be blank', propertyPath=dob, rootBeanClass=class app.model.User, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
ConstraintViolationImpl{interpolatedMessage='must not be blank', propertyPath=name, rootBeanClass=class app.model.User, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
ConstraintViolationImpl{interpolatedMessage='must not be blank', propertyPath=ppsn, rootBeanClass=class app.model.User, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
]
If you have any insight into why this might be happening, I would very much appreciate it.

You have th:object="${user}" in your Thymeleaf template, so I have to assume that you #GetMapping method in your controller has added an instance of User to the Model using addAttribute.
In your #PostMapping, you should also use #ModelAttribute:
#PostMapping("/register_attempt")
public String registerAttempt(#Valid #ModelAttribute("user") User newUser) {
...
You also need to have setters for each field. I see that User has no setName(String name), no setDob(String dob), ...
Some other tips:
Do not create BCryptPasswordEncoder instances in your controller method itself. When you use Spring Boot, this should be an application wide singleton (Called a bean in Spring lingo). Add a class to your application e.g. ReservationSystemApplicationConfiguration which declares this:
#Configuration
public class ReservationSystemApplicationConfiguration {
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Then in your controller, inject the password encoder:
#Controller
#RequestMapping("...")
public class MyController {
private final PasswordEncoder passwordEncoder;
public MyController(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
#PostMapping("/register_attempt")
public String registerAttempt(#Valid #ModelAttribute("user") User newUser) {
String encodedPassword = passwordEncoder.encode(newUser.getPassword());
newUser.setPassword(encodedPassword);
userRepository.save(newUser);
return "registered_successfully";
}
}
A Controller should not directly call the Repository, but use a Service in between.
It is better to use different objects for mapping the form data and for storing the data into the database. See Form Handling with Thymeleaf for more information on how to do that.

Related

Failed to convert value of type 'java.lang.String' to required type 'example.entity.Address'

#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name="addresses")
public class Address {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long add_id;
#Column
private String address;
#Column
private String postalcode;
#Column
private String city;
#Column
private String state;
#Column
private String country;
public Long getAdd_id() {
return add_id;
}
public void setAdd_id(Long add_id) {
this.add_id = add_id;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPostalcode() {
return postalcode;
}
public void setPostalcode(String postalcode) {
this.postalcode = postalcode;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
HTML Page :
<form
method="post"
role="form"
th:action="#{/address/save}"
th:object="${address}"
>
<input type="hidden" th:field="*{add_id}" />
<div class="form-group mb-3">
<label class="form-label">Address</label>
<input
class="form-control"
id="address"
name="address"
placeholder="Enter Address"
th:field="*{address}"
type="text"
/>
<p th:errors = "*{address}" class="text-danger"
th:if="${#fields.hasErrors('address')}"></p>
</div>
<div class="form-group mb-3">
<label class="form-label">Postal Code</label>
<input
class="form-control"
id="postalcode"
name="postalcode"
placeholder="Enter Postal Code"
th:field="*{postalcode}"
type="text"
/>
<p th:errors = "*{postalcode}" class="text-danger"
th:if="${#fields.hasErrors('postalcode')}"></p>
</div>
<div class="form-group mb-3">
<label class="form-label">City</label>
<input
class="form-control"
id="city"
name="city"
placeholder="Enter City"
th:field="*{city}"
type="text"
/>
<p th:errors = "*{city}" class="text-danger"
th:if="${#fields.hasErrors('city')}"></p>
</div>
<div class="form-group mb-3">
<label class="form-label">State</label>
<input
class="form-control"
id="state"
name="state"
placeholder="Enter State"
th:field="*{state}"
type="text"
/>
<p th:errors = "*{state}" class="text-danger"
th:if="${#fields.hasErrors('state')}"></p>
</div>
<div class="form-group mb-3">
<label class="form-label">Country</label>
<input
class="form-control"
id="country"
name="country"
placeholder="Enter Country"
th:field="*{country}"
type="text"
/>
<p th:errors = "*{country}" class="text-danger"
th:if="${#fields.hasErrors('country')}"></p>
</div>
<div class="form-group" style="text-align:center">
<button class="btn btn-success" type="submit">Save</button>
</div>
</form>
Controller:
#Autowired
private AddressRepository addressRepository;
#GetMapping("/address/{id}")
public String addAddress(#PathVariable("id") Long add_id, Model model){
System.out.println("\nInside addressform :\n");
#SuppressWarnings("deprecation")
Address address = addressRepository.getById(add_id);
System.out.println(address.getAdd_id());
System.out.println("add_id : "+add_id);
model.addAttribute("address", address);
return "addressform";
}
// handler method to handle post request to save address
#PostMapping("/address/save")
public String saveAddress(#Valid #ModelAttribute("address") Address address) {
System.out.println("\nInside address save method.");
System.out.println(address.getAdd_id());
System.out.println(address.getAddress());
System.out.println(address.getPostalcode());
System.out.println(address.toString());
addressRepository.save(address);
return "redirect:/users";
}
ERROR :
There was an unexpected error (type=Bad Request, status=400).
Failed to convert value of type 'java.lang.String' to required type 'example.entity.Address'; Failed to convert from type [java.lang.String] to type [java.lang.Long] for value 'madhapur'
org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'example.entity.Address'; Failed to convert from type [java.lang.String] to type [java.lang.Long] for value 'madhapur'
at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:79)
at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:53)
at org.springframework.validation.DataBinder.convertIfNecessary(DataBinder.java:729)
at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.createAttributeFromRequestValue(ServletModelAttributeMethodProcessor.java:142)
at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.createAttribute(ServletModelAttributeMethodProcessor.java:78)
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:147)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:181)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:148)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
Firstly I figured out couple of codes that can cause serious performance issues and is not advised.
Here's a few:
Take off your Generated Getters and Setters and Leave the annotation #Getter and #Setter to do the work.
Take off <input type="hidden" th:field="*{add_id}" /> as it will be auto-generated from your entity Address private Long add_id
Take off address.toString() from your saveAddress controller as this is not needed to parse data to your Entity, your entity receives String already which will be parsed from your form, there is no need to convert with toString again.
One of your error states Failed to convert from type [java.lang.String] to type [java.lang.Long] which simply means you're trying to convert Long to String which should be auto generated by your Entity
I hope this helps!!!

Access Multiple beans using thymeleaf and springboot MVC

Trying to access multiple objects in the POST method using SpringBoot MVC and thymeleaf.
here is the controller.
#Controller
public class PatientController {
ObjectMapper Obj = new ObjectMapper();
#GetMapping("/patient")
public static String patientForm(Model model) {
model.addAttribute("patient", new PatientDataModel());
model.addAttribute("patient1", new PatientDataModel1());
return "patient";
}
#RequestMapping(value="/patient", method=RequestMethod.POST, params="action=Send data to MongoDB cluster")
public static String patientSubmit(#ModelAttribute("patient") PatientDataModel patient, #ModelAttribute("patient1") PatientDataModel patient1, Model model, Object obj ) throws JsonProcessingException {
model.addAttribute("patient", patient);
model.addAttribute("patient1", patient1);
return "result";
}
and here are the views:
patient.html
<form action="#" th:action="#{/patient}" th:object="${patient}" method="post">
<div th:object="${patient1}" >
<p>Patient Id: <input type="text" th:value="${patient.id}" /></p>
<p>Patient Name: <input type="text" th:value="${patient.name}" /></p>
<p>Message: <input type="text" th:value="${patient.content}" /></p>
<p>address: <input type="text" th:value="${patient1.address}" /></p>
</div>
<p><input type="submit" name="action" value="Send data to MongoDB cluster" />
<input type="reset" value="Reset" /></p>
</form>
</div>
and result.html
<div class="starter-template">
<h1>Result</h1>
<p th:text="'id: ' + ${patient.id}" />
<p th:text="'Name: ' + ${patient.name}" />
<p th:text="'content: ' + ${patient.content}" />
<p th:text="'address: ' + ${patient1.address}" />
Submit another message
</div>
and the bean classes are : PatientDataModel.java
public class PatientDataModel {
private long id;
private String content;
private String name;
public PatientDataModel()
{
}
public PatientDataModel(long id, String content, String name)
{
this.id = id;
this.content = content;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
#Override
public String toString()
{
return "Patient [id=" + id + ", firstName=" + name + ", " +
"content=" + content + "]";
}
}
another bean :
public class PatientDataModel1 {
private String address;
#Override
public String toString() {
return "Patient1 [address=" + address + "]";
}
public PatientDataModel1()
{
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
now , the issue is , I need both the beans to be accessible in the GET and POST method.
when I am running the code , it is executing but the beans does not have values , all are null . pls suggest
It will be easiest to have 1 object to find to the form. Create a new class PatientFormData for example that contains all the fields from the 2 objects and convert from/to the objects you have in the get and post methods in your controller.
For example:
public class PatientFormData {
private long id;
private String content;
private String name;
private String address;
public static PatientFormData from(PatientDataModel model,
PatientDataModel1 model1) {
id = model.getId();
content = model.getContent();
name = model.getName();
address = model.getAddress();
}
public PatientDataModel createPatientDataModel() {
PatientDataModel result = new PatientDataModel();
result.setId(id);
result.setContent(content);
result.setName(name);
return result;
}
// getters and setters here
}
Use this in the controller:
#Controller
public class PatientController {
ObjectMapper Obj = new ObjectMapper();
#GetMapping("/patient")
public static String patientForm(Model model) {
PatientFormData formData = PatientFormData.from(new PatientDataModel(), new PatientDataModel1());
model.addAttribute("patientFormData", formData);
return "patient";
}
#RequestMapping(value="/patient", method=RequestMethod.POST, params="action=Send data to MongoDB cluster")
public static String patientSubmit(#ModelAttribute("patientFormData") PatientFormData formData, Model model, Object obj ) throws JsonProcessingException {
PatientDataModel model = formData.createPatientDataModel();
PatientDataModel1 model1 = formData.createPatientDataModel1();
// Do more processing with objects
return "result";
}
Also be sure to correctly use the field binding using *{..}:
<form action="#" th:action="#{/patient}" th:object="${patientFormData}" method="post">
<p>Patient Id: <input type="text" th:value="*{id}" /></p>
<p>Patient Name: <input type="text" th:value="*{name}" /></p>
<p>Message: <input type="text" th:value="*{content}" /></p>
<p>address: <input type="text" th:value="*{address}" /></p>
</div>
<p><input type="submit" name="action" value="Send data to MongoDB cluster" />
<input type="reset" value="Reset" /></p>
</form>
</div>

Thymeleaf: Field error when applying a role to a user (One to Many relationship)

I'm trying to make a Thymeleaf application that can register an account with an attached role that was chosen during registration. Here's a visualization. I will try to add as much relevant code as I can to give a better idea of what's happening and then explain the problem.
User.java:
package com.example.demo.model;
<imports>
#Entity
#Table(name = "user", uniqueConstraints = #UniqueConstraint(columnNames = "account"))
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
private String account;
private String password;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="role_id", nullable=false)
private Role role;
#OneToMany(mappedBy = "user")
private Collection<Comment> comments;
public User() {
}
public User(String firstName, String lastName, String account, String password, Role role,
Collection<Comment> comments) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.account = account;
this.password = password;
this.role = role;
this.comments = comments;
}
<getters and setters>
}
Role.java:
package com.example.demo.model;
<imports>
#Entity
#Table(name = "role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(fetch=FetchType.LAZY, mappedBy = "role")
private Set<User> users = new HashSet<User>(0);
public Role() {
}
public Role(String name, Set<User> users) {
super();
this.name = name;
this.users = users;
}
<getters and setters>
}
Comment.java:
<not important>
UserRepository.java:
package com.example.demo.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.demo.model.User;
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByAccount(String account);
}
RoleRepository.java:
package com.example.demo.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.demo.model.Role;
#Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
}
UserService.java:
package com.example.demo.service;
<imports>
#Service("userService")
public class UserService {
private UserRepository userRepository;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
public UserService(UserRepository userRepository) {
super();
this.userRepository = userRepository;
}
public User findByAccount(String account) {
return userRepository.findByAccount(account);
}
public void saveUser(User user) {
userRepository.save(user);
}
}
RoleService.java:
package com.example.demo.service;
<imports>
#Service
public class RoleService {
private RoleRepository roleRepository;
public RoleService(RoleRepository roleRepository) {
super();
this.roleRepository = roleRepository;
}
}
UserRegistrationController.java:
package com.example.demo.web;
<imports>
#Controller
#RequestMapping("/registration")
public class UserRegistrationController {
private UserService userService;
#ModelAttribute("user")
public User user() {
return new User();
}
#Autowired
RoleRepository roleRepository;
#GetMapping
public String showRegistrationForm(Model model) {
model.addAttribute("roles", roleRepository.findAll());
return "registration";
}
#PostMapping
public String registerUserAccount(User user) {
System.out.println();
userService.saveUser(user);
return "redirect:/registration?success";
}
}
SecurityConfiguration.java (just in case):
package com.example.demo.config;
<imports>
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers(
"/registration**",
"/js/**",
"/css/**",
"/img/**").permitAll().anyRequest().authenticated().
and().formLogin().loginPage("/login").permitAll().
and().logout().invalidateHttpSession(true).clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll();
}
}
Relevant code snippet from registration.html:
<form th:action="#{/registration}" method="post" th:object="${user}">
<div class="form-group">
<label class="control-label" for="firstName">First name</label> <input
id="firstName" class="form-control" th:field="*{firstName}"
required autofocus="autofocus" />
</div>
<div class="form-group">
<label class="control-label" for="lastName">Last name</label> <input
id="lastName" class="form-control" th:field="*{lastName}"
required autofocus="autofocus" />
</div>
<div class="form-group">
<label class="control-label" for="account">Account</label> <input
id="account" class="form-control" th:field="*{account}" required
autofocus="autofocus" />
</div>
<div class="form-group">
<label class="control-label" for="password">Password</label> <input
id="password" class="form-control" type="password"
th:field="*{password}" required autofocus="autofocus" />
</div>
<div class="form-group">
<label class="control-label" for="role">Role</label>
<select class="form-control" th:field="*{role}" id="role">
<option th:each="role: ${roles}" th:value="${role}" th:text="${role.name}"></option>
</select>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success">Register</button>
<span>Already registered? <a href="/" th:href="#{/login}">Login
here</a></span>
</div>
</form>
The problem: Whenever I try to create a user with the selected role I get the following error:
Field error in object 'user' on field 'role': rejected value [com.example.demo.model.Role#6e32df30]; codes [typeMismatch.user.role,typeMismatch.role,typeMismatch.com.example.demo.model.Role,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.role,role]; arguments []; default message [role]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'com.example.demo.model.Role' for property 'role'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Long] for value 'com.example.demo.model.Role#6e32df30'; nested exception is java.lang.NumberFormatException: For input string: "com.example.demo.model.Role#6e32df30"]]
So, I imagine what my code should be doing is using a Role value we get from the form (${role}) and applying it to the Role variable in User.java, connecting them in the relationship. Instead what seemingly happens is that instead of taking a Role value it's taking a String value of "com.example.demo.model.Role#[random id]" from the form.
I'm still very new to Spring Boot and Thymeleaf. Does anyone know what's the problem? I've tried to look up solutions to this exact problem for many hours but I still can't find anything that helps me. Thanks in advance.
Okay, I looked around some more and finally found the resolution myself. Turns out I had to pass th:value="${role.id}" instead of "${role}". Here's the changes I did:
UserRegistrationController.java:
package com.example.demo.web;
<imports>
#Controller
#RequestMapping("/registration")
public class UserRegistrationController {
private UserService userService;
public UserRegistrationController(UserService userService) {
super();
this.userService = userService;
}
#Autowired
RoleRepository roleRepository;
#GetMapping
public String showRegistrationForm(Model model, User user) {
model.addAttribute("roles", roleRepository.findAll());
return "registration";
}
#PostMapping
public String registerUserAccount(#Valid #ModelAttribute("user") User user, BindingResult result) {
userService.saveUser(user);
return "redirect:/registration?success";
}
}
Relevant code snippet from registration.html:
<form th:action="#{/registration}" method="post" th:object="${user}">
<div class="form-group">
<label class="control-label" for="firstName">First name</label> <input
id="firstName" class="form-control" th:field="*{firstName}"
required autofocus="autofocus" />
</div>
<div class="form-group">
<label class="control-label" for="lastName">Last name</label> <input
id="lastName" class="form-control" th:field="*{lastName}"
required autofocus="autofocus" />
</div>
<div class="form-group">
<label class="control-label" for="account">Account</label> <input
id="account" class="form-control" th:field="*{account}" required
autofocus="autofocus" />
</div>
<div class="form-group">
<label class="control-label" for="password">Password</label> <input
id="password" class="form-control" type="password"
th:field="*{password}" required autofocus="autofocus" />
</div>
<div class="form-group">
<label class="control-label" for="role">Role</label>
<select class="form-control" th:field="*{role}" id="role">
<option th:each="role: ${roles}" th:value="${role.id}" th:text="${role.name}"></option>
</select>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success">Register</button>
<span>Already registered? <a href="/" th:href="#{/login}">Login
here</a></span>
</div>
</form>
Hope this helps anyone who was similarly confused.

[Failed to convert property value of type 'java.lang.String[]' to required type 'java.util.List' for property

I am working with Thymeleaf and trying to do some object binding, but I do not know how to do it if I have an object with a list. Let me explain:
My model:
#Entity
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
private String name;
#NotNull
#Lob
private String description;
#NotNull
private Date startDate;
private String status;
#ManyToMany
private List<Role> rolesNeeded;
#ManyToMany
private List<Collaborator> collaborators;
public Project() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public List<Role> getRolesNeeded() {
return rolesNeeded;
}
public void setRolesNeeded(List<Role> rolesNeeded) {
this.rolesNeeded = rolesNeeded;
}
public List<Collaborator> getCollaborators() {
return collaborators;
}
public void setCollaborators(List<Collaborator> collaborators) {
this.collaborators = collaborators;
}
}
My html form:
<form method="post" action="addproject" th:object="${project}">
<div>
<label for="project_name"> Project Name:</label>
<input th:field="*{name}" type="text" name="project_name"/>
</div>
<div>
<label for="project_description">Project Description:</label>
<textarea th:field="*{description}" rows="4" name="project_description"></textarea>
</div>
<div>
<label for="project_status">Project Status:</label>
<div class="custom-select">
<span class="dropdown-arrow"></span>
<select th:field="*{status}" name="project_status">
<option value="active">Active</option>
<option value="archived">Archived</option>
<option value="not_started">Not Started</option>
</select>
</div>
</div>
<div>
<label for="project_roles">Project Roles:</label>
<ul class="checkbox-list">
<li th:each="role : ${roles}">
<input th:field="*{rolesNeeded}" type="checkbox" name="project_roles" th:value="${role}"/>
<span class="primary" th:text="${role.name}"> Developer</span>
</li>
</ul>
</div>
<div class="actions">
<input type="submit" value="Save" class="button"/>
Cancel
</div>
</form>
And I am getting the error:
ERROR!!!!: Field error in object 'project' on field 'rolesNeeded':
rejected value
[com.imprender.instateam.model.Role#145d6cd4,com.imprender.instateam.model.Role#73020d6f];
codes
[typeMismatch.project.rolesNeeded,typeMismatch.rolesNeeded,typeMismatch.java.util.List,typeMismatch];
arguments
[org.springframework.context.support.DefaultMessageSourceResolvable:
codes [project.rolesNeeded,rolesNeeded]; arguments []; default message
[rolesNeeded]]; default message [Failed to convert property value of
type 'java.lang.String[]' to required type 'java.util.List' for
property 'rolesNeeded'; nested exception is
java.lang.IllegalStateException: Cannot convert value of type
'java.lang.String' to required type
'com.imprender.instateam.model.Role' for property 'rolesNeeded[0]': no
matching editors or conversion strategy found]
Basically, as far as I understood, the checkbox input returns a String[], but my object needs a list, so the binding cannot be perfomed.
How could I bind the array in the list? (do you have an example?)
Thank you.
If Your Role bean has active boolean property, You could do something like this (simplified):
<ul class="checkbox-list">
<li th:each="role,stat : ${project.rolesNeeded}">
<input th:field="*{rolesNeeded[__${stat.index}__].active}" type="checkbox"/>
<input th:field="*{rolesNeeded[__${stat.index}__].name}" type="hidden"/>
<span class="primary" th:text="${role.name}">Developer</span>
</li>
</ul>
If it does not, you could store the rolesNeeded in the hidden fields and populate them with javascript.

Parse input fields as a Array list to controller via #RequestMapping

I want to submit all textfields and radiobuttons from my input fields as an ArrayList and parse it to the controller but I don't know really how.
Any help is really appreciated.
Here's what I tried so far:
#RequestMapping(value = "/formdata", method = RequestMethod.POST)
public String formdata(HttpServletRequest req, #AuthenticationPrincipal User currentUser,
#ModelAttribute RegDatas regDatas, Model model) {
// get values as regDatas[0]?
}
Here's my HTML form:
<form method="post" th:action="#{/formdata}" th:object="${regDatas}">
<div class="col-sm-4">
<input th:field="*{regDatas[0].gender}" type="radio" value="MALE" name="gender" disabled="disabled" /><span>Mr</span>
<input th:field="*{regDatas[0].gender}" type="radio" value="FEMALE" name="gender" disabled="disabled"/><span>Frau</span>
<p>Firstname:</p>
<input required="required" type="text" th:field="*{regDatas[0].firstname}" placeholder="Max" disabled="disabled" />
</div>
<div class="col-sm-4">
<input th:field="*{regDatas[1].gender}" type="radio" value="MALE" name="gender" disabled="disabled" /><span>Mr</span>
<input th:field="*{regDatas[1].gender}" type="radio" value="FEMALE" name="gender" disabled="disabled"/><span>Frau</span>
<p>Firstname:</p>
<input required="required" type="text" th:field="*{regDatas[1].firstname}" placeholder="Max" disabled="disabled" />
<button type="submit">Speichern</button>
</div>
</form>
My wrapper class:
public class RegDatas {
private List<RegData> regDatas;
public List<RegData> getRegDatas() {
return regDatas;
}
public void setRegDatas(List<RegData> regDatas) {
this.regDatas = regDatas;
}
}
My class to store values:
public class RegData {
private String email;
private String firstname;
private Gender gender;
public String getEmail() {return email;}
public void setEmail(String email) {this.email = email;}
public String getFirstname() {return firstname;}
public void setFirstname(String firstname) {this.firstname = firstname;}
public Gender getGender() {return gender;}
public void setGender(Gender gender) {this.gender = gender;}
}
This is the error I see if I run it like this:
org.springframework.beans.NotReadablePropertyException: Invalid property 'RegDatas[0]' of bean class [de.ciss.aag.model.domain.RegData]: Bean property 'RegDatas[0]' is not readable or has an invalid getter method
From your comments, I think the problem is that your model is not populated with a RegDatas object. This Baeldung tutorial - Getting Started with Forms in Spring MVC, particularly section 4 is probably a good resource, if you haven't already seen it, but I think you need something like this (from the tutorial) in your controller:
#RequestMapping(value = "/employee", method = RequestMethod.GET)
public ModelAndView showForm() {
return new ModelAndView("employeeHome", "employee", new Employee());
}
But you'll need to add a populated RegDatas instance to your model, instead of the employee, so as a very basic example, this seems to work for me, with your code. There are other ways of populating the model, that I'd recommend researching, but this is probably the most straightforward.
#RequestMapping("/your-page-endpoint")
public ModelAndView get() {
return new ModelAndView("your-view-name", "regDatas", new RegDatas());
}

Categories

Resources