wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

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 the pom.xml look like this (note: I omitted all pieces that are not relevant here, but you will need a complete pom.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.g target8 and target17
  • 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 on 16 April 2024 | Comments (0) | categories: Java WebDevelopment

Comments

  1. No comments yet, be the first to comment