Templating with Thymeleaf: Custom Dialects and More (Part 3)
In the previous articles, we explored the fundamentals of Thymeleaf and focused on reusability with fragments and the Layout Dialect.
Now, in this final installment, we’ll dive into the more advanced features, like creating a custom dialect, processors, and more.
Table of Contents
Custom Dialects and Processors
Custom dialects allow you to extend Thymeleaf’s capabilities by defining new processors (tags, attributes) and functionality that can be reused throughout your templates.
What’s a Dialect?
A Dialect is a collection of custom processors and utility methods that provide extended functionality. With a custom Dialect, you can introduce new attributes or elements that simplify complex tasks, encapsulate common logic, or align your templates more closely with your business requirements.
Creating a Custom Dialect
As our example, we’re going to implement a custom Dialect for formatting dates.
The general idea is to specify a date format and render the current date with it:
To create our custom Dialect, you need to:
- Define processor(s) (i.e., custom tags or attributes).
- Register the dialect to make it available in your templates.
Let’s start by creating a processor!
Creating a Processor
Processors are the units that, well, process different parts of the template. While they’re often used with tags/elements, they are not limited to them.
Thymeleaf offers different types to handle attributes, elements, and even the template structure itself.
For our intended use-case, we want an Element Processor.
To not start from scratch, we extend AbstractElementTagProcessor
.
That way, we only need to call the super
constructor to set up the processor and implement doProcess(...)
ourselves:
The super
call configures the Processor with all the necessities, like which TemplateMode
to work in, how to match, etc.
In our case the Processor looks for an attribute named format
prepended by the dialect’s prefix.
Step 1 is doing some validation.
Instead of throwing a TemplateProcessingException
, we could output an error message or a sensible fallback directly into the output.
Step 2 does the actual work of formatting the current date.
And finally, step 3 is replacing the element’s body content with the formatted date.
The structureHandler
gives us access to the currently processed element and its context.
The second parameter set to false
determines if the new text is processable by other processors.
With a Processor at hand, we can create a Dialect with it.
Creating a Dialect
Let’s create a simple custom Dialect that adds a new attribute for inserting and formatting the current date:
As usual, we won’t implement IDialect
directly but extend AbstractProcessorDialect
instead.
The super
class sets the dialect’s name, prefix, and overall precedence, and getProcessors(...)
returns a Set<IProcessor>
that belong to the dialect.
The Precedence defines in which order processors do their jobs. If a Processor generates content that needs to be processed further, it’s important to let it do its job before the other.
Now, we can add the Dialect to the template engine:
That’s it!
These were all the parts involved in creating a custom Processor, wrapping it into a Dialect, and making it available to our templates.
Our example was quite simplistic; you should definitely check out the other processor types which working on different aspects:
- Elements: Operate on HTML elements or tags (which we’ve seen here)
- Attributes: Triggered by custom or standard attributes on tags
- Text: Process text content inside elements
- Template: Transform an entire template or sections of it
- Document: Modify the entire document before or after processing
- Inline: Manipulate inline expressions or text.
Check out the documentation for more detailed information:
Expression Utility Objects
Expression utility objects are utility objects that provide additional functionality. They simplify common tasks, like String operations or formatting values.
These objects are accessed using the #
(number sign) syntax, like #numbers
.
Thymeleaf has 17 built-in expression objects:
#execInfo
: Access template/processing information#messages
: Obtaining externalized messages#dates
: Working withjava.util.Date
objects#calendars
: Analogous to#dates
but forjava.util.Calendar
objects#numbers
: Formatting numeric values#uris
: Escaping/unescaping URI/URL parts#conversions
: Use the Conversion Service for type coercing#temporals
: Working with thejava.time
API#strings
: Manipulating Strings#objects
: Working with objects in general#bools
: Bool operations#arrays/lists/sets/maps
: Working with different container types#aggregates
: aggregates on arrays or collections#ids
: Helper for dealing withid
attributes
You find all of the available objects and their methods here:
In addition to expression utility objects, there are also expression basic objects:
#ctx
: TheIContext
of the template#vars/root
: Synonyms for#ctx
(not recommended)#locale
: Direct access to `Locale of the current request
Let’s look at a few of the utility objects in more detail.
Working with Strings
The #strings
object helps to manipulate Strings directly within a template.
It provides common String operations, making it easier to perform tasks like concat
, trim
, replace
, capitalize
, or create a substring
without resorting to custom utility classes or doing it beforehand in Java.
One practical example is concatenating Strings, which is null
-friendly, as it replaces them with an empty String
instead of rendering the word “null”:
As you see, we can mix literals with expressions for the arguments.
Besides manipulating Strings, there are methods for conditionals, too:
Boolean contains(target, fragment)
Boolean containsIgnoreCase(target, fragment)
Boolean equals(first, second)
Boolean equalsIgnoreCase(first, second)
Boolean startsWith(target, prefix)
Boolean endWith(target, prefix)
You should definitely check out the documentation for the full list.
Working with Dates
The #dates
utility object gives us way to format java.util.Date
instance, get the current date (so much for our custom dialect), and get specific properties of a date.
One common use case is formatting a date:
As expressions can be nested, we could format the current date as we want by using two calls:
We can access a date’s component quite easily to create easier expression than using format
:
Integer day(target)
Integer month(target)
String monthName(target)
String monthNameShort(target)
Integer year(target)
Integer dayOfWeek(target)
String dayOfWeekName(target)
String dayOfWeekNameShort(target)
Integer hour(target)
Integer minute(target)
Integer second(target)
Integer millisecond(target)
Unlike #strings
, the #dates
object has no methods for conditionals.
If we want to use java.util.Calendar
instead of Date
, we can use #calendars
which has an analogous API.
And if we have to deal with the java.time
API, we can use #temporals
.
Creating Your Own Utility Object
While the built-in expression objects cover many common use cases, we might encounter scenarios where a custom expression object is preferable, like repetitive domain-specific tasks. By creating our own object, we can encapsulate specific logic and expose it directly in our templates.
Custom expression utility objects are based around IExpressionObjectFactory
that has three methods in need of implementation:
The factory provides on or more expression objects based on their names.
The buildObject(...)
method creates new instances of an expression object based on its name.
The isCachable(...)
method dictates if the expression object needs to be recreated every time it’s used, or if it can be cached for a template.
The expression utility object type itself can be anything, as all its non-static
public
methods are exposed to the template via the registered name.
After creating the type and factory, we can add it to a Dialect:
Just register the Dialect with the template engine, and the expression utility object is available throughout our Thymeleaf templates, providing reusable utility functions tailored to your application’s needs.
Custom Template Resolving
Thymeleaf provides a range of template resolvers out of the box, like ClassLoaderTemplateResolver
or FileTemplateResolver
, which we looked at in the first part of this series.
But what if our templates are stored in a custom location, like a database or a remote service?
Don’t fret, we can easily create our own template resolver to load templates from any source!
Custom Processing for Data Transformation
As an example, we pretend to implement a database-based loader. However, the actual loading isn’t shown, as we’re only interested in the Thymeleaf-relevant parts for this article.
Like with many other customizations shown previously, we don’t start with the lowest level, the ITemplateResolver
, but with an abstract
class that provides a lot of functionality to get started: AbstractConfigurableTemplateResolver
.
This way, we just need a constructor and a single method for a minimalistic implementation, but still can override more methods if needed:
Then, we need to register the resolver with the template engine:
That’s it!
Caching with Custom Template Resolvers
If we only need a binary choice for caching, we could call setCacheable(boolean)
either in the constructor, or before registering the resolver.
But there’s an option to have more fine-grained control by overriding computeValidity(...)
:
The method arguments don’t provide much to make an informed decision, and the built-in resolvers mostly use pattern-matching and if the resolver is cacheable in general.
The templateResolutionAttributes
are set if the template engine is called with a TemplateSpec
instead of the template name:
Controlling caching balances performance with freshness, ensuring we get don’t get outdated template content. However, caching can be tricky at times, so proceed with caution.
Conclusion
Throughout this article series, we’ve explored the many facets of working with Thymeleaf: from the basics of template creation to diving deeper into advanced topics like custom Dialects, Expression Objects, and template resolving.
Thymeleaf has proven itself to be a versatile and robust solution for any templating need. Its natural templating approach makes collaboration with other people simpler without sacrificing any features. The extensive expression language and built-in processors make Thymeleaf a good fit for applications of any size and complexity.
One of Thymeleaf’s standout features is its extensibility. Creating custom processors or expression objects can tailor Thymeleaf to quite domain-specific needs. This ensures that Thymeleaf is adaptable and future-proof to unique project requirements.
Even though it wasn’t part of the series, it still should be mentioned that Thymeleaf offers seamless integration with the Spring ecosystem. Its native support, including form binding and validation, makes it an ideal companion for Spring-based web applications.
In conclusion, Thymeleaf combines the best of both worlds: the simplicity of natural templates with the power of a fully-featured and easily customizable template engine. Whether you’re building a small project or a large-scale enterprise application, I absolutely to check out Thymeleaf!