andrewJames
andrewJames

Reputation: 22032

Unexpected result using Thymeleaf arithmetic

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

Answers (1)

andrewJames
andrewJames

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

Related Questions