Skip to content
This repository was archived by the owner on Apr 26, 2026. It is now read-only.
This repository was archived by the owner on Apr 26, 2026. It is now read-only.

BindingResult never makes it into the template so that errors cannot be displayed #169

Description

@odrotbohm

I have a Spring Data form binding interface like this:

interface GuestbookForm {

	/**
	 * Returns the value bound to the {@code name} attribute of the request.
	 * 
	 * @return
	 */
	@NotBlank
	String getName();

	/**
	 * Returns the value bound to the {@code text} attribute of the request.
	 * 
	 * @return
	 */
	@NotBlank
	String getText();

	…
}

and a Spring MVC controller method using that interface to bind the form as well as an Errors instance to collect binding errors:

@PostMapping(path = "/guestbook")
String addEntry(@Valid GuestbookForm form, Errors errors, Model model) {

	if (errors.hasErrors()) {
		return guestBook(model, form);
	}

	guestbook.save(form.toNewEntry());
	return "redirect:/guestbook";
}

While the Errors object that gets handed into the controller carries the errors, in the form below, they're inaccessible, i.e. #fields.hasErrors('*') returns false, error CSS classes are not set etc:

<form method="post" role="form" class="gb-form" id="form" th:action="@{/guestbook}" th:object="${form}">
		
	<div th:if="${#fields.hasErrors('*')}">Entry has errors!</div>
		
	<div class="form-group">
		<label for="name" th:text="#{guestbook.form.name}">Name</label><br /> 
		<input class="form-control" type="text" th:field="*{name}" th:errorclass="fieldError" /><br /> 
	</div>

	<div class="form-group">
		<label for="text" th:text="#{guestbook.form.text}">Text</label><br /> 
		<textarea th:field="*{text}" th:errorclass="fieldError" class="form-control"></textarea><br /> 
	</div>

	<input type="submit" class="btn btn-default" th:value="#{guestbook.form.submit}" value="Senden" />

</form>

Here's what I've noticed and debugged already:

  • The controller method argument not carrying an @ModelAttribute is not an oversight. It needs to be that way so that the standard Spring form binding doesn't get active but Spring Data's special interface binding.
  • I've debugged into FieldUtils.getBindStatusFromParsedExpression(…) and can see that the requestContext that's obtained in the first step doesn not contain the Errors instance that the controller sees but an empty one.
  • This leads me to the assumption that Thymeleaf sort of loses the connection between the model object form and the associated Errors instance.
  • I failed to find the code that makes Thymeleaf aware of the binding result produced by Spring MVC in the first place.

You can find a reproducing sample project here. Steps to reproduce:

$ git clone https://github.com/st-tu-dresden/guestbook
$ cd guestbook
$ git co thymeleaf
$ mvn spring-boot:run

Browse to http://localhost:8080, submit the empty form. See the page redisplayed without any errors. Put a breakpoint in GuestbookContoller.addEntry(…) and see the Errors instance actually carrying errors.

Metadata

Metadata

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions