Reputation: 22032
I have a Thymeleaf template, without any Spring or SpEL being used - just the Thymeleaf standard dialect.
The relevant portion of the template is:
<div>
<span>Inactive: </span>
<span th:text="${total - active}"></span>
</div>
Test 1
If my model is populated as follows:
model.put("total", 10);
model.put("active", 7);
then I get the expected result:
<div>
<span>Inactive: </span>
<span>3</span>
</div>
Test 2
But if either (or both) of the model values is null:
model.put("total", null);
model.put("active", 7);
then I get an unexpected result. With the above data, I get:
<div>
<span>Inactive: </span>
<span>-7.0</span>
</div>
In other words, the null
is evaluated as the floating point 0.0
, and the result is therefore calculated as -7.0
.
I would have expected it to throw a runtime error, caused by trying to perform arithmetic on a null value.
Test 3
If I change the template slightly and use the same data as test 2:
<div>
<span>Inactive: </span>
<span th:text="${total} - ${active}"></span>
</div>
I get the following runtime error - which is what I expected for test 2:
org.thymeleaf.exceptions.TemplateProcessingException: Cannot execute subtraction: operands are "null" and "7"
Why does test 2 generate valid HTML with an unexpected calculation result, instead of throwing an error?
(I understand I need to handle these null
values up-front to avoid issues, but this silent non-failure is a problem.)
Upvotes: 1
Views: 899
Reputation: 22032
Quick Answer
You should handle null
values explicitly up-front, as noted in the question.
You should also be aware of the inconsistent behavior between Thymeleaf's minus operator, and OGNL's minus operator.
Recommendation: Use the Thymeleaf minus operator.
A more detailed explanation follows...
Thymeleaf and OGNL
When you use the standard Thymeleaf dialect, expressions inside the opening ${
and closing }
are handled by OGNL. From the official documentation:
This is a variable expression, and it contains an expression in a language called OGNL (Object-Graph Navigation Language) that will be executed on the context variables map...
You can read about OGNL and its syntax here. Thymeleaf version 3.0.12 uses the ognl-3.1.26.jar
library.
So, when you use ${total - active}
, that entire expression, including the subtraction, is handled by OGNL.
However, when you use ${total} - ${active}
, that is actually two separate OGNL expressions, with a minus sign in between them.
In the first case, it is OGNL's responsibility to perform the subtraction. But in the second case, the subtraction is performed by Thymeleaf, after it has delegated evaluation of the two ${...}
expressions to OGNL.
Thymeleaf generally shows the same behavior as OGNL for the majority of its operators. But in this specific case, the Thymeleaf minus operator behaves differently from the OGNL minus operator, when handling null
values.
I would argue that the Thymeleaf behavior is the less surprising approach.
Spring and SpEL
If you use Spring with Thymeleaf, then all expressions inside ${
and }
are delegated to SpEL (the Spring Expression Language) - and OGNL is not used at all.
In this case the expression ${total - active}
will no longer "succeed" at runtime. Instead it will throw a SpEL exception:
org.springframework.expression.spel.SpelEvaluationException: EL1030E: The operator 'SUBTRACT' is not supported between objects of type 'null' and 'java.lang.Integer'
So, SpEL and the Thymeleaf standard dialect are consistent with each other - and OGNL is in the minority.
Does OGNL really evaluate null as zero, here?
Yes - and we can demonstrate that using OGNL in a simple Java program:
The dependency:
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>3.3.0</version>
<!-- same behavior:
<version>3.2.21</version>
-->
</dependency>
The Java demo:
import ognl.Ognl;
import ognl.OgnlException;
public class NullDemo {
public void run() throws OgnlException {
// this uses a null context (2nd parameter), because we have
// a simple hard-coded subtraction expression:
Object result = Ognl.getValue("null - 7", null);
System.out.println(result);
}
}
The output is: -7.0
, the same as test #2 in the question.
Acknowledgements
I am extremely grateful to the Thymeleaf team for pointing me in the right direction for this problem:
Unexpected result from subtraction using nulls
Postscript
The OGNL expression:
"1 / null"
evaluates to Java's Double.POSITIVE_INFINITY
, for the same reasons as the above behavior - where null
is converted to 0.0
.
Upvotes: 1