Maven jobs and Java versions compatibility

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.

  • Soon (July-September 2022 timeframe) Jenkins will require 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).

IMPORTANT: 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

NOTE: 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="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://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="http://maven.apache.org/TOOLCHAINS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/TOOLCHAINS/1.1.0 http://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="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">
  <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

Have more questions?

7 Comments

  • 0
    Avatar
    Bollapu Harinathareddy

    Hi, 

    I am facing similar issue in Jenkins 2.18 version, will you please is this solution will work or not. I am using maven 3.3.3 and JDK 1.8. 

     

    Thanks in advance Harinatha

     

  • 0
    Avatar
    Arnaud Héritier

    Hi Bollapu,

     

      Yes the principle remains the same. If you have this error this is because you configured a Maven job with a Java version < 7. Jenkins >= 1.612 and thus 2.18 cannot natively use Maven jobs with a Java version inferior to 7. THus the build is trying to use the Java version of your agent which is necessarily superior or equal to 7.

     

    Best regards

  • 0
    Avatar
    Oleg Nenashev

    This guide is a bit outdated. If you use CJE based on Jenkins LTS 2.60.x or newer, Java 8 is required in the case of Maven Jobs. It is documented on the plugin Wiki page: https://wiki.jenkins.io/display/JENKINS/Maven+Project+Plugin

  • 0
    Avatar
    Arnaud Héritier

    It was fixed Oleg. Thanks

  • 0
    Avatar
    Fenix32 Gonz

    Hi, I am updating from Jenkins 1.624.4 to 2.89.2 with my projects on JDK1.6.

    In my poms

                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>2.3.2</version>
                    <configuration>
                        <source>1.6</source>
                        <target>1.6</target>
                    </configuration>
                </plugin>

    I made them work creating new FreeStyle-Jobs configured as JDK1.6 without -Dmaven.compiler.source=1.6 -Dmaven.compiler.target=1.6. I have some questions

    • Is it necessary/recommendable to continue with The toolchains solution?
    • Could I use Maven-Jobs instead FreeStyle-Jobs after apply The toolchains solution?

     

    Thank you a lot for the tutorial and the help ^_^

    Edited by Fenix32 Gonz
  • 0
    Avatar
    Arnaud Héritier

    Hi Fenix,

      As you configured the maven compiler in the pom you effectively don't need to use 

    -Dmaven.compiler.source=1.6 -Dmaven.compiler.target=1.6

      No the toolchain solution isn't mandatory and is useful only if you want to come back to use Maven-Jobs instead FreeStyle-Jobs. By using toolchains you'll be able to use Maven-Jobs by letting them running on Java 8 (required by jenkins) but all tools (javac, javadocs, ...) will use Java 6. Using a freestyle project with Java 6 give you the same result if you don't need absolutely some features provided by the Maven jobs.

    BR.

  • 0
    Avatar
    Jesse Glick

    The resolution

    All Maven Jobs have to be configured to use at the minimum the version required by Jenkins

    is incorrect. The whole point of the message printed in the build log

    Retrying with slave Java and setting compile/test properties to point to <path_to_the_java_vm-used_by_your_slave>.

    is that you do not have to do anything, in the common case: if your Maven project is configured with a JDK older than the minimum currently required by Jenkins controllers & agents, then the Maven plugin for Jenkins will automatically relaunch the Maven JVM with a newer Java runtime but configure common Maven mojos to actually use your configured JDK for build steps.

    This automated workaround may not be complete, depending on the details of your project. See https://issues.jenkins.io/browse/JENKINS-68878?focusedCommentId=427502&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-427502 for a case where a JAX-WS issue in Jenkins 2.357 (requiring Java 11) was resolved by updating a mojo version in the POM.

    When you have time to do it, the recommendation is to switch to Pipeline. https://plugins.jenkins.io/pipeline-maven/ provides much of the functionality of the Maven plugin for Jenkins in a more sustainable way.

Please sign in to leave a comment.