Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Introduction

In this tutorial, we’ll learn how to check if a variable is defined in Thymeleaf using three different methods. For this purpose, we’ll use Spring MVC and Thymeleaf to build a simple web application with a single view that displays the server date and time if a given variable is set.

2. Setup

Before diving into the methods, we need to do some initial setup. Let’s start with the Thymeleaf dependencies:

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>3.1.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.1.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>

Now, let’s create the checkVariableIsDefined view:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      th:with="lang=${#locale.language}" th:lang="${lang}">
<head>
    <title>How to Check if a Variable is Defined in Thymeleaf</title>
</head>
<body>

<!-- we'll add here the relevant code for each method -->

</body>
</html>

Let’s also define two new endpoints for this view:

@RequestMapping(value = "/variable-defined", method = RequestMethod.GET)
public String getDefinedVariables(Model model) {
    DateFormat dateFormat = 
      DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, Locale.getDefault());
    model.addAttribute("serverTime", dateFormat.format(new Date()));
    return "checkVariableIsDefined.html";
}

@RequestMapping(value = "/variable-not-defined", method = RequestMethod.GET)
public String getNotDefinedVariables(Model model) {
    return "checkVariableIsDefined.html";
}

The first endpoint loads the checkVariableIsDefined view with the serverTime variable defined, whereas the latter endpoint loads the same view without the variable defined.

This setup will help us test the methods presented in the following sections.

3. Using the #ctx Object

The first method we’ll explore uses the context object, which contains all the variables the Thymeleaf template engine needs to process templates, including a reference to the Locale used for externalized messages. The context is an implementation of the IContext interface for standalone applications or the IWebContext interface for web applications.

We can access the context object in a Thymeleaf template using the #ctx notation. Let’s add the relevant code to the checkVariableIsDefined view:

<div th:if="${#ctx.containsVariable('serverTime')}" th:text="'Server Time Using the #ctx Object Is: ' + ${serverTime}"/>

Now, let’s write two integration tests to verify this method:

private static final String CTX_OBJECT_MSG = "Server Time Using the #ctx Object Is: ";

@Test
public void whenVariableIsDefined_thenCtxObjectContainsVariable() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/variables-defined"))
      .andExpect(status().isOk())
      .andExpect(view().name("checkVariableIsDefined.html"))
      .andExpect(content().string(containsString(CTX_OBJECT_MSG)));
}

@Test
public void whenVariableNotDefined_thenCtxObjectDoesNotContainVariable() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/variables-not-defined"))
      .andExpect(status().isOk())
      .andExpect(view().name("checkVariableIsDefined.html"))
      .andExpect(content().string(not(containsString(CTX_OBJECT_MSG))));
}

4. Using the if Conditional

The following method uses the if conditional. Let’s update the checkVariableIsDefined view:

<div th:if="${serverTime}" th:text="'Server Time Using #th:if Conditional Is: ' + ${serverTime}"/>

If the variable is null, the if conditional is evaluated as false.

Now, let’s take a look at the integration tests:

private static final String IF_CONDITIONAL_MSG = "Server Time Using #th:if Conditional Is: ";

@Test
public void whenVariableIsDefined_thenIfConditionalIsTrue() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/variable-defined"))
      .andExpect(status().isOk())
      .andExpect(view().name("checkVariableIsDefined.html"))
      .andExpect(content().string(containsString(IF_CONDITIONAL_MSG)));
}

@Test
public void whenVariableIsNotDefined_thenIfConditionalIsFalse() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/variable-not-defined"))
      .andExpect(status().isOk())
      .andExpect(view().name("checkVariableIsDefined.html"))
      .andExpect(content().string(not(containsString(IF_CONDITIONAL_MSG))));
}

The if conditional is evaluated as true if any of the following conditions is true:

  • the variable is a boolean with the value true
  • the variable is a non-zero number
  • the variable is a non-zero character
  • the variable is a string different than “false”, “off”, “no”
  • the variable is not a boolean, a number, a character, or a string

Note that if the variable is set, but has the value “false”, “no”, “off”, or 0, then the if conditional is evaluated as false, which might cause some undesired side effects if our intention is to only check if the variable is set. Let’s illustrate this by updating the view:

<div th:if='${"false"}' th:text='"Evaluating \"false\"'/>
<div th:if='${"no"}' th:text='"Evaluating \"no\"'/>
<div th:if='${"off"}' th:text='"Evaluating \"off\"'/>
<div th:if="${0}" th:text='"Evaluating 0"'/>

Next, let’s create the integration test:

@Test
public void whenVariableIsDefinedAndNotTrue_thenIfConditionalIsFalse() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/variable-defined"))
      .andExpect(status().isOk())
      .andExpect(view().name("checkVariableIsDefined.html"))
      .andExpect(content().string(not(containsString("Evaluating \"false\""))))
      .andExpect(content().string(not(containsString("Evaluating \"no\""))))
      .andExpect(content().string(not(containsString("Evaluating \"off\""))))
      .andExpect(content().string(not(containsString("Evaluating 0"))));
}

We could address this issue by checking that the variable is not null:

<div th:if="${serverTime != null}" th:text="'Server Time Using #th:if Conditional Is: ' + ${serverTime}"/>

5. Using the unless Conditional

The last method uses unless which is the inverse of the if conditional. Let’s update the view accordingly:

<div th:unless="${serverTime == null}" th:text="'Server Time Using #th:unless Conditional Is: ' + ${serverTime}"/>

Let’s also test whether this method produces the expected results:

private static final String UNLESS_CONDITIONAL_MSG = "Server Time Using #th:unless Conditional Is: ";

@Test
public void whenVariableIsDefined_thenUnlessConditionalIsTrue() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/variable-defined"))
      .andExpect(status().isOk())
      .andExpect(view().name("checkVariableIsDefined.html"))
      .andExpect(content().string(containsString(IF_CONDITIONAL_MSG)));
}

@Test
public void whenVariableIsNotDefined_thenUnlessConditionalIsFalse() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/variable-not-defined"))
      .andExpect(status().isOk())
      .andExpect(view().name("checkVariableIsDefined.html"))
      .andExpect(content().string(not(containsString(UNLESS_CONDITIONAL_MSG))));
}

6. Conclusion

In this article, we’ve learned three methods for checking if a variable is defined in Thymeleaf. The first method uses the #ctx object and the containsVariable method, whereas the second and last methods use the conditional statements if and its inverse unless.

As always, the complete code can be found over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.