Thymeleaf - An Expression of Surprise

12 Nov 2021

Summary

Try to avoid using hyphens in your model names.

Details

Let’s assume we have plain old Thymeleaf (no Spring, no SpEL).

Thymeleaf expressions such as ${foo} are basically OGNL expressions which retrieve their values from a Map<String, Object> (the “model”) provided in your Java code:

Java
1
2
Map<String, Object> model = new HashMap<>();
model.put("foo", "someValue");

Example 1 - no expression:

HTML
1
<div th:text=""></div>

This results in a TemplateProcessingException: Could not parse as expression: “”.

You cannot have an empty expression.

Example 2 - empty literal:

HTML
1
<div th:text="''"></div>

This is not an expression, but a string literal (the two single quotes). It’s an empty string literal in this case, and generates an empty div: <div></div>.

Example 3 - undefined expression value:

HTML
1
<div th:text="${doesnotexistinmodel}"></div>

As its name suggests, this expression uses a value which does not exist in the model. You may have expected it to throw an error, but it actually generates an empty div: <div></div>.

Example 4 - the null literal:

HTML
1
<div th:text="null"></div>

null is a Thymeleaf literal representing a null value. It generates an empty div: <div></div>.

Example 5 - a text literal:

HTML
1
<div th:text="undefined"></div>

This matches the rules for a Thymeleaf text literal. It generates a div containing the literal value: <div>undefined</div>.

Example 6a - expression addition/concatenation:

HTML
1
<div th:text="${foo} + ${bar}"></div>

If foo and bar were assigned numeric values then they are summed. If they were assigned string values then they are concatenated.

But for this example, we will assume they were assigned null values in the model, or simply not defined at all (and therefore default to null).

This generates <div>nullnull</div>. The two null values are treated as strings and concatenated. This may or may not come as a surprise.

Example 6b - a variation on example 6a:

HTML
1
<div th:text="${foo + bar}"></div>

Again assuming foo and bar are both null, this also generates <div>nullnull</div>. This is as equally (un-)surprising as example 6a.

Example 7a - expression subtraction:

HTML
1
<div th:text="${foo} - ${bar}"></div>

Still assuming null values, this throws a TemplateProcessingException: Cannot execute subtraction: operands are “null” and “null”.

Fair enough - you cannot perform arithmetic on nulls.

Example 7b - a variation on example 7a:

HTML
1
<div th:text="${foo - bar}"></div>

This creates <div>0</div>.

Wait… what?

Null minus null equals zero? Why did that happen?

I don’t know why that happened - but it certainly surprised me.

I have asked for insights here.

Update:

I got a response to my ticket - many thanks to the Thymeleaf team for that. The basic answer is: The OGNL version of my surprising example does not behave as expected - but the Thymeleaf version does. The expectation is that arithmetic performed on a null value should throw an exception, whereas the OGNL version of the expression evaluates to 0.

For more details on this, see my article Thymeleaf vs. SpEL vs. OGNL.

Conclusion

Surprise!

The example in 7b can lead to silent bugs.

Consider how ${foo - bar} is the same as ${foo-bar} - it’s just that the first has some white space which makes it a bit clearer that there is a subtraction being used.

But what if you have a variable in your model like this:

Java
1
2
Map<String, Object> model = new HashMap<>();
model.put("foo-bar", 123);

You might expect ${foo-bar} to evaluate to 123 - but it still evaluates to 0, assuming the same scenario as example 7b. The subtraction operator takes precedence over the hyphen in the variable name.

Even if your model value was a string:

Java
1
model.put("foo-bar", "some text");

then the Thymeleaf expression would still evaluate to 0.

That’s why I always try to avoid using hyphens in my model names.

Concatenation Surprise!

Even more subtle is the case where you may be using a pre-processor __${...}__ to build the expression to be evaluated - for example, to dynamically build a model field name using concatenation. In this case, the hyphen may be in the Thymeelaf not in the model - but the surprising result will be the same.