Search code examples
spring-bootspring-mvcthymeleaf

field error with binding when using thymeleaf form and pass to controller


I'm working on a Reddit-clone with spring-mvc which has a link and comment entity.

The problem is adding a new comment function.

When I try to submit the form, In my controller, the bindingResult has an error.

The log info showed that there is a Field error in object 'comment' on-field 'link'. The error is due to type mismatch from String to Link.

I couldn't figure out where the String comes from and why the link field is not bound correctly.

A Link and an empty Comment with an association to the link was added to the model with get mapping for the current view page

I'm new to spring and being struggled for this, I appreciate any help.

Below is the code for controller and thymeleaf as well as my link and comment entity

@GetMapping("/link/{id}")
public String read(@PathVariable Long id,Model model) {
        Optional<Link> optionalLink = linkRepository.findById(id);
        if( optionalLink.isPresent() ) {
            Link link = optionalLink.get();
            Comment comment = new Comment();
            comment.setLink(link);
            model.addAttribute("comment",comment);
            model.addAttribute("link",link);
}

@PostMapping("/link/comments")
public String addComment(Comment comment, BindingResult bindingResult) {
    log.info(comment.toString());
    if( bindingResult.hasErrors() ) {
        log.info(bindingResult.toString());
        log.info("Something went wrong.");
    } else {
        log.info("New Comment Saved!");
        commentRepository.save(comment);
    }
    return "redirect:/link/" + comment.getLink().getId();
}

<form id="frmAddComment" method="POST" th:action="@{/link/comments}" th:object="${comment}">
      <input type="hidden" th:field="*{link}"/>
      <div class="form-group">
          <textarea class="form-control" id="comment" rows="3" th:field="*{body}"></textarea>
      </div>
      <button type="submit" class="btn btn-primary">Add Comment</button>
</form>
@Entity
@Getter@Setter
@RequiredArgsConstructor
@NoArgsConstructor
public class Comment extends Auditable{

    @Id
    @GeneratedValue
    private long id;
    @NonNull
    private String body;

    @ManyToOne
    @NonNull
    private Link link;
}
public class Link extends Auditable{

    @Id
    @GeneratedValue
    private long id;

    @NonNull @NotEmpty(message = "Please enter a title")
    private String title;

    @NonNull @URL(message = "Please enter a valid url")
    private String url;

    // comments
    @OneToMany(mappedBy = "link")
    private List<Comment> comments = new ArrayList<>();
}

Solution

  • HTML only works with Strings. The problem is that Spring does not know how to convert from a String to a Link.

    It is easier to create a dedicated form data object and use that in our @GetMapping and @PostMapping.

    public class AddCommentToLinkFormData {
        private long linkId;
        @NotNull
        @Size(min=1,max=1000) // Set this to the values you want
        private String comment;
    
        // add getters and setters 
    }
    

    In your controller:

    public class MyController {
        
        @GetMapping("...") // URL that you want to use
        public String linkInfo(Model model) {
            model.addAttribute("formData", new AddCommentToLinkFormData());
            return "..." // name of your Thymeleaf view
        }
    
        @PostMapping("/link/comments")
        public String addComments(Model model,
                      @Valid @ModelAttribute("formData") AddCommentToLinkFormData formData,
                      BindingResult bindingResult) {
            if( bindingResult.hasErrors() ) {
                return "..." // name of your Thymeleaf view. Use `#fields.hasErrors('comment')` in your HTML if you want to show a message if the comment String is not valid.
            } else {
                service.addCommentToLink(formData.getLinkId(), formData.getComment());
                return "redirect:/link/" + formData.getLinkId();
            }
       }
    }
    

    The HTML should be:

    <form id="frmAddComment" method="POST" th:action="@{/link/comments}" th:object="${formData}">
          <input type="hidden" th:field="*{linkId}"/>
          <div class="form-group">
              <textarea class="form-control" id="comment" rows="3" th:field="*{comment}"></textarea>
          </div>
          <button type="submit" class="btn btn-primary">Add Comment</button>
    </form>