javamavenbackwards-compatibility

How to ensure I don't inadvertently depend on code only available in a more recent version of Java?


I am creating a set of libraries for in-house use, and I have a reactor pom setup to build all these libraries.

I want to be able to build them all at once, with one command.

However, the contexts where the libraries will be used have different base Java runtimes.

Because I want to build them all at once I need to have the Java version I run this build as the most recent Java version I'm targeting.

However, while I know that I can set the compiler target in each of my projects to match the java version where that project will be used how can I guarantee that I won't inadvertently use a method or class in that project that is only available in the more recent version of Java I'm using to build these libraries?


Solution

  • javac 9 introduced a --release flag that will, to quote the documentation, "Compile for the specified Java SE release".

    The maven-compiler-plugin also supports this feature, and allows you to use the <release> tag in its configuration to pass a value down to the Java compiler.

    For example, you could configure your compiler plugin to compile for Java 9:

    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.13.0</version>
      <configuration>
        <encoding>UTF8</encoding>
        <release>9</release>
      </configuration>
    </plugin>
    

    Alternatively, you can configure this behavior by setting the built-in maven.compiler.release property:

    <properties>
      <maven.compiler.release>9</maven.compiler.release>
    </properties>
    

    Now let's see what it does. Consider, for example, that you have a piece of code that uses List's addFirst method, introduced in Java 21:

    List<Integer> myList = new ArrayList<>();
    myList.addFirst(1);
    

    When compiling it with this configuration, it will explicitly be compiled to be compatible with Java 9's API, and the compilation will fail:

    [INFO] --- compiler:3.13.0:compile (default-compile) @ samplejava ---
    [INFO] Recompiling the module because of changed source code.
    [INFO] Compiling 1 source file with javac [debug release 9] to target/classes
    [INFO] -------------------------------------------------------------
    [ERROR] COMPILATION ERROR : 
    [INFO] -------------------------------------------------------------
    [ERROR] /Users/mureinik/src/samplejava/src/main/java/org/example/Main.java:[9,15] cannot find symbol
      symbol:   method addFirst(int)
      location: variable myList of type java.util.List<java.lang.Integer>
    [INFO] 1 error
    

    This is despite using a modern Java version that most certainly has that method in its JDK:

    āžœ  samplejava mvn --version
    Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
    Maven home: /opt/homebrew/Cellar/maven/3.9.9/libexec
    Java version: 23.0.2, vendor: Homebrew, runtime: /opt/homebrew/Cellar/openjdk/23.0.2/libexec/openjdk.jdk/Contents/Home
    Default locale: en_IL, platform encoding: UTF-8
    OS name: "mac os x", version: "15.5", arch: "aarch64", family: "mac"
    

    EDIT:
    For completion's sake - the --release flag supports Java versions 8 and above. If you need to support an older Java version, you can use the Animal Sniffer Maven Plugin.