Removing Joda from Spring Boot
The problem
We recently migrated a medium-sized Java project to Spring Boot 2 from version 1. One of the challenges was migrating to the JDK date-time library from Joda. It turns out that Spring Boot 2 has excellent native support for JDK date-times, as does Jackson (JSON) and Hibernate (database), the default technologies offered by Spring Boot 2 for these features.
The migration itself went smoothly, which is unsurprising given the fantastic work of Stephen Colebourne in designing JDK date-time support based on his authorship of Joda.
So we looked at disabling Joda completely in our Gradle build. The most concise approach we found was:
configurations { compile.exclude group: 'joda-time' }
This removed Joda completely from configurations (classpaths) related to Java. However, this had unintended side effects:
- During tests, we needed Joda in the runtime classpath for a 3rd-party library, OpenSAML
- During boot run (running the app), we needed Joda in the classpath for another 3rd-party library, SpringFox
We easily found a workaround for SpringFox, but not for OpenSAML.
(If you're curious, yes, we do intent to migrate from OpenSAML 2
(desupported in 2016) to OpenSAML 3; however, we would like
spring-security-saml2-core
support first.)
A solution in progress
The exclusion in the compile
configuration does
exactly what we need: Joda disappears! But what to do
about SpringFox and OpenSAML?
For the Spring Boot runtime classpath, there is another concise solution, though finding it was rather troublesome, and it is not well-documented by Pivotal or in Stack Overflow.
First, we setup another classpath of our own making named bootRuntime:
configurations { // Other parts of "configurations", including the Joda exclusion from above bootRuntime // Synthetic configuration for deps needed *only* to launch app }
Then we added Joda to that synthetic classpath relying on Spring Boot plugin's definition for the version of Joda to use:
dependencies { // Other parts of "dependencies" bootRuntime 'joda-time:joda-time' }
Lastly, we taught Spring Boot to include this synthetic classpath when launching our app (this was the trickiest part):
bootJar { bootInf { from configurations.bootRuntime into 'lib' } } bootRun { classpath += configurations.bootRuntime }
This adds Joda to the runtime classpath for both the single "fat
jar" built by Spring (bootJar
), and when launching
the app on the command line with gradle (bootRun
).
Unless you are a heavy Gradle user, from ... into ...
syntax may be unfamiliar: this copies the jars in the synthetic
configuration into the fat jar at the location Boot expects to find
them. The "'lib'" is literally a directory location within the
jar. Useful magic, but a bit obtuse. The outcome:
$ jar tf build/libs/the-project.jar | grep joda-time BOOT-INF/lib/joda-time-2.9.9.jar
As a matter of fact, Joda is the very last file in the boot jar,
a suggestion that it was added by our bootInf
section
after the Boot plugin built the jar.
(Our workaround is intentionally small. If we're unable to make
it work, we'll switch to brute-force library exclusions in our
dependencies lists. The goal is to prevent accidental import
from Joda, for example, of LocalDate
.)
Remaining work
For running the boot app, this solution is great: it is small, readable, easy to maintain, and it works. However, for tests which exercise our user authentication with OpenSAML, it fails. Joda is not in the test classpath, and we cannot use or mock OpenSAML methods which use Joda types.
Barring another magical solution like bootRuntime
,
we'll fall back on manually excluding Joda from each dependency,
and adding it back in to the test classpath. A pity given how
pithy the solution is with exclusion from the
compile
configuration.