Maven jobs and Java versions compatibility

Article ID:217517477
5 minute readKnowledge base

Issue

Maven jobs are reporting an error

java.lang.UnsupportedClassVersionError: Bad version number in .class file

or something like

ERROR: JENKINS-18403 JDK 5 not supported to run Maven; retrying with slave Java and setting compile/test properties to point to <path_to_the_java_vm-used_by_your_agent>

or something like

ERROR: Invalid project setup: jenkins/security/MasterToSlaveCallable : Unsupported major.minor version 52.0
ERROR: [JENKINS-18403][JENKINS-28294] JDK 'XXXXXX' not supported to run Maven projects.
ERROR: Maven projects have to be launched with a Java version greater or equal to the minimum version required by the controller.
ERROR: Use the Maven JDK Toolchains (plugin) to build your maven project with an older JDK.
ERROR: Retrying with slave Java and setting compile/test properties to point to <path_to_the_java_vm-used_by_your_slave>.

Environment

Explanation

Because java serialized classes are exchanged between Jenkins controller and Maven jobs running on agents, it is required that the JVM used to launch Maven on agents is superior or equal to the version of Java which the Jenkins controller is using.

  • Jenkins >= 1.520 (1.531.1 for LTS) requires Java 6 thus Maven jobs must be launched with a JDK >= 6.

  • Jenkins >= 1.612 (1.625.1 for LTS) requires Java 7 thus Maven jobs must be launched with a JDK >= 7.

  • Jenkins >= 2.54 (2.60.1 for LTS) requires Java 8 thus Maven jobs must be launched with a JDK >= 8.

  • Jenkins >= 2.357 (2.361.1 for LTS) requires Java 11 thus Maven jobs must be launched with a JDK >= 11.

This constraint was firstly reported for the Jenkins upgrade from Java 5 to Java 6 as JENKINS-18403 thus the error message

ERROR: JENKINS-18403 JDK 5 not supported to run Maven; retrying with slave Java and setting compile/test properties to point to <path_to_the_java_vm-used_by_your_slave>

This one was wrongly implemented thus for the upgrade to Java 7 you are receiving the same error about JDK 5 while it is JDK 6 (See JENKINS-28294).

Resolution

All Maven Jobs have to be configured to use at the minimum the version required by Jenkins like described above. To ease the change you can use the Configuration Slicing Plugin. This will allow your build to properly start, but you may face various problems because you will build your project with a more recent version of your JDK than what you are really targeting.

There are several solutions to avoid this problem.

On Jenkins side

Maven jobs configuration

The simplest workaround could be use the JDK version required by Jenkins and to define the properties maven.compiler.source and maven.compiler.target locally in your maven job settings (or globally in Jenkins global configuration for MAVEN_OPTS if you have only one java version target).

This will work if your maven jobs are using the default Maven behavior to configure the java level rather than enforcing the java level configuration in compiler and other plugins.
  • To target Java 6 with a higher JDK use -Dmaven.compiler.source=1.6 -Dmaven.compiler.target=1.6

  • To target Java 7 with a higher JDK use -Dmaven.compiler.source=1.7 -Dmaven.compiler.target=1.7

  • To target Java 8 with a higher JDK use -Dmaven.compiler.source=1.8 -Dmaven.compiler.target=1.8

  • …​

If the JDK used is a version 9 or greater you should use the release option of javac (maven.compiler.release as maven property) which is replacing maven.compiler.source and maven.compiler.target:

  • To target Java 6 with a JDK >= 9 use -Dmaven.compiler.release=6

  • To target Java 7 with a JDK >= 9 use -Dmaven.compiler.release=7

  • To target Java 8 with a JDK >= 9 use -Dmaven.compiler.release=8

  • …​

javac in JDK 11 supports releases: 6, 7, 8, 9, 10, 11

References:

Other options

In Jenkins, instead of using Maven Jobs you can use FreeStyle jobs with a Maven build step. This solution requires a manual recreation of jobs. FreeStyle jobs will offer fewer features than Maven jobs, but they’ll support to launch Maven on any version of java.

The same applies to pipeline jobs which are allowing to define your jobs as code.

At Apache Maven level

Before anything you need to configure the maven compiler plugin to target your oldest version of Java even if you are using a more recent JDK. If you didn’t configure (directly or in a parent) the compiler plugin you can just add in your pom (for Java 6):

<project xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
...
  <properties>
    <maven.compiler.source>1.6</maven.compiler.source>
    <maven.compiler.target>1.6</maven.compiler.target>
  </properties>
...

If the compiler plugin is already reconfigured in your project or in a parent pom you may have to use another property or declare the configuration options of the plugin in the plugins or pluginManagement settings.

Sadly configuring the compiler options are often not enough to ensure that you will produce binaries compatible with your target JRE. For example, you can use APIs (methods) provided by the new JDK and which are not available in the older version.

To avoid this kind of issue there are 2 solutions at Apache Maven level which will allow you to launch Apache Maven with a Java version superior to the one targetted by your application but without risking to produce an incompatible binary. With theses solutions you’ll have to update your build but you’ll be able to continue to use your Maven Jobs.

References:

The release option for JDK >= 9

In replacement of maven.compiler.source and maven.compiler.target properties as described above you use the maven.compiler.release like this:

<project xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
...
  <properties>
    <maven.compiler.release>6</maven.compiler.release>
  </properties>
...

References:

The animal-sniffer solution for JDK < 9

In your build you add a control using the Animal Sniffer plugin to avoid to use in your code some APIs provided by the version of Java used to build. This solution isn’t 100% safe (it controls only the signatures of methods not their semantics) but it covers a large part of classical errors to build an application for an older version of Java.

<project xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns="https://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-enforcer-plugin</artifactId>
        <version>1.4</version>
        <executions>
          <execution>
            <id>check-java-compat</id>
            <goals>
              <goal>enforce</goal>
            </goals>
            <phase>process-classes</phase>
            <configuration>
              <rules>
                <checkSignatureRule implementation="org.codehaus.mojo.animal_sniffer.enforcer.CheckSignatureRule">
                  <signature>
                    <groupId>org.codehaus.mojo.signature</groupId>
                    <artifactId>java16</artifactId>
                    <version>1.0</version>
                  </signature>
                </checkSignatureRule>
              </rules>
            </configuration>
          </execution>
        </executions>
        <dependencies>
          <dependency>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>animal-sniffer-enforcer-rule</artifactId>
            <version>1.14</version>
          </dependency>
        </dependencies>
      </plugin>
...
    </plugins>
  </build>
</project>

A full sample is available here

The toolchains solution

About toolchains :

With toolchains, Apache Maven will be able to run on a different (version) of the JVM than the JDK used to build your project. It will allow to run Apache Maven on the same JVM version than your Jenkins controller (for exemple Java JRE 7) while it will use another JDK to build your application (for example Java JDK 5). With this strategy the targeted version of the JDL is used

On Jenkins side you will need to perform the following actions:

  • Your Maven Job project will be configured to use a JDK 7. You will use the Tool Environment Plugin to install an additional JDK 6 on your agent.

  • You will use the Config File Provider Plugin to define and install a toolchain.xml file used by maven to define where the JDK6 is installed

<toolchains xmlns="https://maven.apache.org/TOOLCHAINS/1.1.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="https://maven.apache.org/TOOLCHAINS/1.1.0 https://maven.apache.org/xsd/toolchains-1.1.0.xsd">
  <toolchain>
    <type>jdk</type>
    <provides>
      <version>1.6</version>
    </provides>
    <configuration>
      <jdkHome>/home/opt/jdk1.6</jdkHome>
    </configuration>
  </toolchain>
</toolchains>

On Maven side:

  • You will configure the maven-toolchain-plugin to tell to Maven to use a JDK 6 to perform all Java related tasks (javac, javadoc …​)

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
...
  <build>
    <plugins>
      <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>1.6</version>
            </jdk>
          </toolchains>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

A full sample is available here