Skip to content
TaeyoungKim.dev

Spring Model and Thymeleaf: Pass controller data into HTML safely

Java/SpringWritten 3 min readTaeyoungKim
LinkedInX

If a value created in a controller does not appear in HTML, remember that the browser cannot directly see a Java object. The controller puts display data in a Model, and the template engine uses that model to produce the final HTML.

Model carries data between the controller and template

The diagram follows the name value, not the whole greeting: the controller adds Jin under name, selects the hello view, and Thymeleaf writes the name into HTML text.

This controller adds one name to the model and returns the view hello:

java
@Controller
class HelloController {
    @GetMapping("/hello")
    String hello(@RequestParam(name = "name", defaultValue = "guest") String name, Model model) {
        model.addAttribute("name", name);
        return "hello";
    }
}

The view name points to src/main/resources/templates/hello.html. Because name may come from a URL parameter, it is not automatically trustworthy. For a page requiring data lookup or authorization, use a value checked on the server rather than blindly treating the request parameter as verified data.

Thymeleaf renders text with th:text

In the template, th:text reads name from the model:

html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
  <h1 th:text="|Hello, ${name}!|">Hello</h1>
</body>
</html>

The browser receives rendered HTML after the server processes the template. Not seeing a Java Model object in developer tools is expected; check the actual response text instead.

At /hello?name=Jin, the HTML contains Hello, Jin!. At /hello, it uses the default guest. Checking both cases distinguishes a parameter-to-model problem from a template expression problem. th:text escapes text such as a user-supplied <script> instead of executing it as HTML. Escaping still does not replace authorization or input validation.

Keep display data separate from domain objects

A single string is enough here. For lists or profiles, passing an entire entity to the template can expose fields that should remain internal, such as password hashes or authorization state. A view DTO makes display responsibility explicit:

java
record ProfileView(String displayName, String email) {}

model.addAttribute("profile", new ProfileView("Kim", "[email protected]"));

The address is illustrative. Do not put real account details into teaching examples or templates unnecessarily.

Make missing values an explicit decision

If name can be absent, define a default in the controller or show a deliberate fallback in the template. Whether absence is normal or an error is better decided by application logic than buried in complex template conditions.

MockMvc can check both the selected view and model value:

java
mockMvc.perform(get("/hello").param("name", "Mina"))
    .andExpect(status().isOk())
    .andExpect(view().name("hello"))
    .andExpect(model().attribute("name", "Mina"))
    .andExpect(content().string(containsString("Hello, Mina!")));

If the model assertion passes but response-text assertion fails, inspect the template and th:text. If the model assertion fails, inspect the controller's attribute assignment first.

Key takeaways

Model carries controller data to a template, and Thymeleaf's th:text renders that value into HTML text. Pass only display fields, validate untrusted input where needed, and test the view, model attribute, and resulting body separately.

Author

TaeyoungKim

Connecting technical foundations with implementation, verification, and production decisions.

#Spring MVC#Model#Thymeleaf#Templates

Read next