Maven build with multiple Java versions
Imagine, you are tasked with maintaining a Java application that needs to run on more than one Java version. You want to ensure that it compiles, tests and builds on all of them.
This is our story, buckle up, there are a few moving parts
The big picture
- We use Apache Maven to drive the project using the
pom.xml
- The Maven Toolchains plugin controls the Java versions
- Using
<properties> ... </properties>
and Build Profiles to adjust conditions for processing - Annotatiosn like
@Only8
and@Only17
help to qualify tests - Our build tool (Jenkins or Github Actions) will use a container provided (in our case based on Redhat UBI)
Getting the image ready
In our image we need Java8, Java17, Maven and a toolchains.xml
file. We will install maven in /opt/maven
and thus the toolchains.xml
needs to be in /opt/maven/conf
. on your developer workstation you put it in ~/.m2/
. The toolchains.xml
needs to have the exact path of the JDKs. Since this can vary between maintenance releases, we grab the path from an actual install (see shell script below).
When you install more than one Java version you can use update-alternatives --config java
to see the available versions. e.g
Selection Command
-----------------------------------------------
* 1 java-1.8.0-openjdk.x86_64 (/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.402.b06-2.el8.x86_64/jre/bin/java)
+ 2 java-17-openjdk.x86_64 (/usr/lib/jvm/java-17-openjdk-17.0.10.0.7-2.el8.x86_64/bin/java)
Note: the name doesn't change per Java version, but the path contains patch levels. So we need to automate processing. We start with an template xml file which I keep in ~/install
:
<?xml version="1.0" encoding="UTF-8"?>
<toolchains>
<!-- JDK toolchains -->
<toolchain>
<!-- x86_64 -->
<type>jdk</type>
<provides>
<version>17</version>
<vendor>openjdk</vendor>
</provides>
<configuration>
<jdkHome>openjdk17</jdkHome>
</configuration>
</toolchain>
<toolchain>
<!-- x86_64 -->
<type>jdk</type>
<provides>
<version>1.8</version>
<vendor>openjdk</vendor>
</provides>
<configuration>
<jdkHome>openjdk8</jdkHome>
</configuration>
</toolchain>
</toolchains>
The placeholders openjdk8
and openjdk17
will get replaced with the actual path. On Redhat that looks like this (note: you might need additional yum
installed components):
# Install software packages
yum install -y unzip zip curl nano
# Java install for each version
yum install -y java-1.8.0-openjdk-headless java-1.8.0-openjdk-devel
yum install -y java-17-openjdk-headless java-17-openjdk-devel
# Capture the java 8 version
update-alternatives --set java java-1.8.0-openjdk.x86_64
j8dir=$(readlink -f $(which java))
# Make sure the default java is 17
update-alternatives --set java java-17-openjdk.x86_64
j17dir=$(readlink -f $(which java))
# install maven
mkdir -p /opt
curl -sS -o apache-maven-3.9.6-bin.tar.gz https://dlcdn.apache.org/maven/maven-3/3.9.6/binaries/apache-maven-3.9.6-bin.tar.gz
tar xf apache-maven-3.9.6-bin.tar.gz -C /opt
ln -s /opt/apache-maven-3.9.6 /opt/maven
rm apache-maven-3.9.6-bin.tar.gz
ln -s /opt/maven/bin/mvn /usr/bin/mvn
# Create updated maven toolchains.xml file
rm -f /opt/maven/conf/toolchains.xml
cp ~/install/toolchains.xml /opt/maven/conf/toolchains.xml
sed -i '' -e "s|openjdk8|$j8dir|g" /opt/maven/conf/toolchains.xml
sed -i '' -e "s|openjdk17|$j17dir|g" /opt/maven/conf/toolchains.xml
The pom.xml
file
We have 3 areas we need to adjust
<properties>
: Any value you want to manipulate from the outside or want to overwrite in a profile should be in the properties section<plugins>
: To activate the toolchains plugin<profiles>
: to provide version specific values. We found that quite a number of dependencies require Java versions greater than eight. On Java17 we want to use those, while on Java8 sticking to the last version. The relevant parts of thepom.xml
look like this (note: I omitted all pieces that are not relevant here, but you will need a completepom.xml
):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<properties>
<!-- Java 17 compatible values -->
<java.version>17</java.version>
<mockito.version>5.11.0</mockito.version>
<flexmark.version>0.64.8</flexmark.version>
<saxon.version>12.4</saxon.version>
<ical.version>4.0.0-rc5</ical.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-toolchains-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<goals>
<goal>toolchain</goal>
</goals>
</execution>
</executions>
<configuration>
<toolchains>
<jdk>
<version>${java.version}</version>
<vendor>openjdk</vendor>
</jdk>
</toolchains>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>Java8</id>
<properties>
<!-- Java 8 compatible values -->
<java.version>1.8</java.version>
<mockito.version>4.11.0</mockito.version>
<flexmark.version>0.62.2</flexmark.version>
<saxon.version>11.6</saxon.version>
<ical.version>4.0.0-rc3</ical.version>
</properties>
</profile>
</profiles>
</project>
Now you are all set. Running mvn clean package
will process your app with Java 17, while mvn clean package -PJava8
will process with Java 8. Note that after a maven run you need to copy the target
directory to preserve your version.
There are a few more ideas to contemplate:
- activate the profile based on the Java version and use
-Djava.version=1.8
on the command line - Switch the
target
directory based on the Java version to e.gtarget8
andtarget17
- In a multi-module setups you might want to conditionally include version specific child projects
The use of @Anotations
will be covered in another story at another time.
As usual YMMV
Posted by Stephan H Wissel on 16 April 2024 | Comments (0) | categories: Java Maven WebDevelopment