Spring Boot + Thymeleaf HTML Form Handling (Part 1)

Marko Milicevic
6 min readOct 13, 2017

Update: Part-2 article layers on JSR-303 Form Validation.

HTML form handling comes up often, so i thought it would be useful to prepare a Snack for common cases to refer to.

For this article i will focus on all the core HTML Form Elements, how to render them with HTML+Thymeleaf and the data/interaction integration between Spring Boot and Thymeleaf.

Here is the code for the full project…

If you are not familiar with Thymeleaf, here is a great tutorial in the context of Spring…

Command Object

The Spring MVC ‘Model’ is responsible for representing the full state of the View that is rendered. The Model is a Map that can contain simple attributes, but in the case of Form state that can change a good pattern to follow is representing the Form state by a single object — the Command Object (aka. form backing Bean).

In most cases the Command Object can be a simple POJO DTO, but using an object house the state also opens the possibility to add Command behavior (methods) to enrich more complex scenarios.

Lombok (side-trail)

If you love reducing code-noise, you must checkout Project Lombok.

You can get similar effect using Groovy’s built-in @Canonical annotation, but Lombok allows you to get similar benefits while remaining pure-Java.

The installation of Eclipse-support is not as simple as it should be. Here is a good guide…

For this Snack i am using the @Data annotation to get auto-generated toString(), equals(), hashCode(), getters*, setters*, constructor.

That is amazing value and noise reduction! No muss, no fuss!

How boring and ugly is it to manually write all that boilerplate?!

FYI, the Lombok @Value annotation is @Data + immutable (cool beans:-).

OK, here is the Command object that includes all the Form state (state that can change)…

import lombok.Data;@Data
public class FormCommand {
String textField;

String textareaField;

String datetimeField;

String colorField;

boolean singleCheckboxField;

String[] multiCheckboxSelectedValues;

String radioButtonSelectedValue;
String dropdownSelectedValue;
}

Form Elements

Without further adieu, i will include key snippets for each HTML/Thymeleaf Form Element type, without going into too much detail about Thymeleaf-specific markup.

HTML Form

The th:object attribute references the Model key that references the Command object (aka. Form Bean) that represents your Form state. In this case model[‘command’] -> <instance of FormCommand>.

<form action="#" th:action="@{/fooform}" th:object="${command}" method="post">
...
</form>

Simple Text Field

The good’ole simple entry field.

th:field=”*{textField}” is a reference to command.textField.

<p>Simple Text Field: <input type="text" th:field="*{textField}" /></p>

Text Area

<p>Text Area Field: <textarea th:field="*{textareaField}" ></textarea></p>

Date Field

<p>Date Field: <input type="datetime-local" th:field="*{datetimeField}" /></p>

Color Field

<p>Color Field: <input type="color" th:field="*{colorField}" /></p>

Single Checkbox Field

<div>
<label th:for="singleCheckboxField" th:text="'Single Checkbox Field:'"></label>
<input type="checkbox" th:field="*{singleCheckboxField}" />
</div>

Multi-Select Checkbox Set


<ul>
<li th:each="checkboxValue: ${multiCheckboxAllValues}">
<input type="checkbox"
th:field="*{multiCheckboxSelectedValues}"
th:value="${checkboxValue}" />
<label
th:for="${#ids.prev('multiCheckboxSelectedValues')}"
th:text="${checkboxValue}"></label>
</li>
</ul>

OK, this dawg’s getting a little more interesting.

*{multiCheckboxSelectedValues} is a reference to…

model.command.multiCheckboxSelectedValues

Which is a (String[]) attribute of FormCommand. The array contains every selected checkbox value.

th:for="${#ids.prev('multiCheckboxSelectedValues')}"

The #ids.prev() function is used to generate a unique Input field id attribute by appending a base-1 sequence id to the name multiCheckboxSelectedValues (id=”multiCheckboxSelectedValues1", id=”multiCheckboxSelectedValues2", etc…).

<li th:each="checkboxValue: ${multiCheckboxAllValues}">

An <li> element is created for every multiCheckboxAllValues, which is the complete list of checkbox options (not just the selected values).

Where does that value come from?

Stay tuned, we will see more details below with the discussion of @ModelAttribute.

Single-Select Radio Button Set

<ul>
<li th:each="radioValue: ${singleSelectAllValues}">
<input type="radio"
th:field="*{radioButtonSelectedValue}"
th:value="${radioValue}" />
<label
th:for="${#ids.prev('radioButtonSelectedValue')}"
th:text="${radioValue}"></label>
</li>
</ul>

The Radio Set is is similar to the Checkbox Set, except FormCommand.radioButtonSelectedValue is of type String (rather than String[]), since we are constrained to select only a single value from the total set of available Radio values.

Single-Select Dropdown Field

<select th:field="*{dropdownSelectedValue}">
<option
th:each="dropdownValue: ${singleSelectAllValues}"
th:value="${dropdownValue}"
th:text="${dropdownValue}" ></option>
</select>

Marking-up a Dropdown is similar to Checkbox Sets and Radio Button Sets, with some minor rearranging.

Spring Controller Integration

Getting “AllValues” for Multi-Valued Fields

Back to the question of where multiCheckboxAllValues came from for the Checkbox case (the list of all possible checkbox values to select from)?

What we do is go to the Controller that provides the Model for the Form view/page, then we add a Controller method that is annotated with @ModelAttribute(“multiCheckboxAllValues”).

@ModelAttribute("multiCheckboxAllValues")
public String[] getMultiCheckboxAllValues() {
return new String[] {
"Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday"
};
}

This tells the framework that any HTTP Request to the Controller will first call getMultiCheckboxAllValue() and pre-populate the Model with the result…

model[“multiCheckboxAllValues”] = getMultiCheckboxAllValues()

Once the Controller forwards to the Thymeleaf View with the completed Model , then multiCheckboxAllValue will be available for rendering the set of Checkboxes.

POSTing the Command Object

How does the POSTed Command Object make it to the Controller?

All you need to do is add the Command Object as a parameter of the Controller handler method. As long as the Command Class attributes have names consistent with the HTTP Request param/path variable names, then the framework will automatically create an instance of the Command Object populated with the POSTed form data.

@PostMapping("/fooform")
public String foobarPost(
@ModelAttribute("command") FormCommand command,
Model model ) {
...

In this case, POSTing to “/fooform” will automatically create a populated instance of FormCommand.

This example also illustrates the optional use of @ModelAttribute.

The annotation (in the context of a Controller Request Handler parameter), makes explicit that the Command Object will be stored in the Model using the key “command”.

model[“command”] = <FooCommand instance>

Unit Testing

The project also demonstrates a couple of Unit Tests to verify the Controller of the Form page, including the construction and movement of the FormCommand object.

@Test
public void fooFormTest() throws Exception {
mockMvc.perform(get("/fooform"))
.andExpect(model().attribute("command", any(FormCommand.class)))
.andExpect(view().name("fooForm"))
.andExpect(status().isOk());
}
@Test
public void fooFormPostTest() throws Exception {
this.mockMvc
.perform(post("/fooform")
.param("textField", "foobar")
.contentType(MediaType.APPLICATION_FORM_URLENCODED))
.andExpect(status().is3xxRedirection())
.andExpect(flash().attribute("command", hasProperty("textField", equalTo("foobar"))));
}

Happy Snacking!-)

--

--