Garret Wilson
Garret Wilson

Reputation: 21386

Working around lack of Optional support in Thymeleaf

It's 2020 and Thymeleaf still doesn't support Optional from Java 8, released six year ago. What are the workarounds?

I'm using Spring Boot 2.3.3.RELEASE with Thymeleaf. I have an object of type Foo that the controller provides via the model/session as foo. It has a method findName() that returns Optional<String>. So I can put this in my template:

[[${foo.findName()}]]

Assuming the name is "bar", that shows:

Optional[bar]

That works, but obviously it's not what I want.

So I tried what I would do in Java. The following should print simply the name (e.g. foo), or _____ if the name is missing:

[[${foo.findName().orElse("_____")}]]

Unfortunately that produces an exception:

org.thymeleaf.exceptions.TemplateProcessingException: Could not parse as expression: ""

Trying to use conditions, this will provide the value if it is present:

[[${foo.findName().isPresent()} ? ${foo.findName().get()}]]

But for some reason this throws an exception:

[[${foo.findName().isPresent()} ? ${foo.findName().get()} : '_____']]

First, I'm not sure why the example using orElse() doesn't work, as the Optional instance is an object like any of the others. Secondly, does anyone know a workaround? Thirdly, does anyone know of a workaround that is concise and doesn't take up three times the code?

(What we really need is native support in Thymeleaf so that ${foo.findName()} ?: '_____' will just work naturally.)

Upvotes: 2

Views: 3271

Answers (3)

Nikolas
Nikolas

Reputation: 44408

Since Thymeleaf uses Spring Epression language while integrated with Spring, the Thymeleaf itself is not to blame. As of the current version of Spring, the SpEl doesn't support Java 8 Optional yet, here is the GitHub issue #20433.

Possible workarounds require different expressions:

  • When you have a default value (the _ escaping issue is resolved throught a comment)
    <span th:text="${foo.findName().orElse('---')" />
    
  • When you don't want to render an enpty (or default) line, you can hide it using th:if:
    <th:block th:if="${foo.findName().isPresent()}">
        <span th:text="${foo.findName().get()" /> 
    </th:block>`
    

Upvotes: 3

As workaround for the Java Optionals funcionality use the "Elvis Operator" and the "Safe Navigation Operator" of SpringEL:

Safe Navigation Operator: If myOptional is null, this will return null instead of an error:

myOptional.orElse(null)?.myProperty

That's equivalent to this in Java:

myOptional.map(ObjectType::getMyProperty).orElse(null);

Elvis Operator: If myOptional or myProperty is null, this will return "alternativeContent":

myOptional.orElse(null)?.myProperty?:"alternativeContent"

That's equivalent to this in Java:

myOptional.map(ObjectType::getMyProperty).orElse("alternativeContent");

Easier might be, not to pass Optionals to Thymeleaf alltogether and only use the Operators mentioned above:

myObject?.myProperty
myObject?.myProperty?:"alternativeContent"

Upvotes: 0

Garret Wilson
Garret Wilson

Reputation: 21386

The only thing I can get to work so far is this:

<th:block th:switch="${foo.findName().isPresent()}">
  <th:block th:case="true">[[${foo.findName.get()}]]</th:block>
  <th:block th:case="*">_____</th:block>
</th:block>

Horrors—so verbose! Is that the best that can be done?

Upvotes: 1

Related Questions