When configuring Thymeleaf for a Spring project, you need the org.thymeleaf:thymeleaf-spring4
dependency that contains required libraries for Thymeleaf support in Spring 4.
This dependency will transitively load other dependencies as well. Among others, it loads the Thymeleaf dependency itself, for which as we will see, it is important to be in the correct version.
You can check out the dependencies of your Maven project by running the mvn dependency:tree command.
[INFO] \- org.thymeleaf:thymeleaf-spring4:jar:3.0.1.RELEASE:compile
[INFO] +- org.thymeleaf:thymeleaf:jar:3.0.1.RELEASE:compile
[INFO] | +- org.attoparser:attoparser:jar:2.0.0.RELEASE:compile
[INFO] | \- org.unbescape:unbescape:jar:1.1.3.RELEASE:compile
[INFO] \- org.slf4j:slf4j-api:jar:1.6.6:compilecons
This list of transitive dependencies actually contains the correct ones for the currently latest Thymeleaf for Spring 4. Note the 3.0.1.RELEASE
version on the second line.
Confusing Maven by bringing in another Thymeleaf (without knowing…)
It can happen that if you also use some other dependency that loads the org.thymeleaf:thymeleaf
, then there is a chance, that it will load an older version and overwrite the correct one. This happened in my case as well.
I used the Spring Platform BOM to get a whole lot of compatible dependency versions already set up in my project. However, the org.thymeleaf:thymeleaf
transitive dependency version coming from it caused a problem, because it was a much older version and did not contain all the necessary classes. The resulting dependency tree looked like this:
[INFO] +- javax.servlet:javax.servlet-api:jar:3.1.0:compile
[INFO] \- org.thymeleaf:thymeleaf-spring4:jar:3.0.1.RELEASE:compile
[INFO] +- org.thymeleaf:thymeleaf:jar:2.1.5.RELEASE:compile
[INFO] | \- org.unbescape:unbescape:jar:1.1.0.RELEASE:compile
[INFO] \- org.slf4j:slf4j-api:jar:1.7.21:compile
Note the old 2.1.5.RELEASE
version for org.thymeleaf:thymeleaf
.
This old version was missing some classes that are present in the latest, because the API have been changed in the new version. Namely AbstractConfigurableTemplateResolver
and TemplateResolver
. Because these were not defined, I got class not found errors when I was trying to build the project.
Solution
So, the Platform BOM is unfortunately referencing an older version of Thymeleaf, we have two ways we can go about solving this.
1. Override the version by specifying it
If you are using the Platform BOM as a parent of your POM, you can just add a property to ovverride the version number to the correct one, like this:
<thymeleaf.version>3.0.1.RELEASE</thymeleaf.version>
If you are using it as a dependency, in your dependencyManagement
section like:
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>2.0.7.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Then you have to add the Thymeleaf as a dependency as well to override the version:
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>${thymeleaf.version}</version>
</dependency>
I think these solutions are a bit fragile so I would recommend the next option I show you.
2. Switch to a lighter weight option
If you look at the contents and parents of the Platform BOM, you can see that it brings in a whole lot of dependencies, and some of them with outdated versions. For example Spring is used with a version that is a couple of releases older than the current one.
As you can see this can cause problems.
I think the best way to solve this, is to remove the Spring Platfom BOM and instead use the lighter weight Spring Framework BOM. It only contains dependency versions for the Spring Framework itself, and it references the latest from each module.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring.framework.bom.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
If you check it this way, the transitive dependency that caused the problem is not loaded.
Deeper look into the cause
If we take a look at where did this old dependency came from, it might be hard to find.
There is no sign of it in the Spring Platform BOM, however is you go up to it’s parent (org.springframework.boot:spring-boot-starter-parent
), and then to it’s parent (org.springframework.boot:spring-boot-dependencies
), there you can find this old Thymeleaf version.
You can check it out here if you would like.
So the takeaway from this, is if you are searching for a transitive dependency, always make sure to also check in the parent and it’s parent and so on.