Maven Java Version Configuration ignored by Eclipse/Idea
Asked Answered
C

1

9

I have:

<build>
  <pluginManagement>
     <plugins>
        <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
           <version>3.1</version>
           <configuration>
              <source>1.6</source>
              <target>1.6</target>
           </configuration>
        </plugin>
     </plugins>
  </pluginManagement>
</build>

Yet I have no problem declaring:

public enum DirectoryWatchService {

    INSTANCE;

    private java.util.Optional<String> test;
    private java.nio.file.Files files;
}

Eclipse doesn't bother. IntelliJ neither. Even Maven does not bother. I can even do a mvn clean package. Builds the darn thing without any warning whatsoever.

Concepcion answered 10/3, 2016 at 10:26 Comment(4)
Remove <pluginManagement> and you'll see errors as high as the sky.Isbell
in general, source/target are often misunderstood, they check against syntax, not API used. you should always use the bootstrap option to make sure to compile and be consistent with your target java version. Alternatively, the animal-sniffer maven plugin is the way to go to break the build if you use java 7/8 API while compiling with target 6Tendril
Furthermore you should use toolchain to correctly define the used JDK independent of the JDK which is used to run Maven itself...Chilcote
@Tunaki: Sadly, no. I do not.Concepcion
T
21

You are hitting the cross compilation misunderstanding of source/target options. Using a major version in your classpath (JDK 7 or 8) but wishing to compile against a minor version (6 in your case).
Compilation will be fine, but the you will have errors at runtime (i.e. NoClassDefFoundError or NoSuchMethodError, probably also the more generic LinkageError).

Using source/target, the Java compiler can be used as a cross-compiler to produce class files runnable on JDKs implementing an earlier version of the Java SE specification.

The common belief is that using two compiler options would be enough. However, The source option specifies against which version we are compiling while the target option specifies the lowest Java version to support.

The compiler works on bytecode generation and source and target are used to generate compatible bytecode during cross-compilation. However, Java API are not handled by the compiler (they are provided as part of the JDK installation, the famous rt.jar file). The compiler doesn’t have any knowledge of API, it just compiles against the current rt.jar. Hence, when compiling with target=1.6 using JDK 1.7, the compiler will still point to the JDK 7 rt.jar.

So, how can we actually have a correct cross-compilation?

Since JDK 7, javac prints a warning in case of source/target not in combination with the bootclasspath option. The bootclasspath option is the key option in these cases to point to the rt.jar of the desired target Java version (hence, you need to have the target JDK installed in your machine). As such, javac will effectively compile against the good Java API.

But that may still not be enough!

Not all Java API comes from the rt.jar. Other classes are provided by the lib\ext folder. A further javac option is used for that, extdirs. From official Oracle docs

If you are cross-compiling (compiling classes against bootstrap and extension classes of a different Java platform implementation), this option specifies the directories that contain the extension classes.

Hence, even using source/target and bootclasspath options, we may still miss something during cross-compilation, as also explained in the official example which comes with the javac documentation.

The Java Platform JDK's javac would also by default compile against its own bootstrap classes, so we need to tell javac to compile against JDK 1.5 bootstrap classes instead. We do this with -bootclasspath and -extdirs. Failing to do this might allow compilation against a Java Platform API that would not be present on a 1.5 VM and would fail at runtime.

But.. it may still not be enough!

From Oracle official documentation

Even when the bootclasspath and -source/-target are all set appropriately for cross-compilation, compiler-internal contracts, such as how anonymous inner classes are compiled, may differ between, say, javac in JDK 1.4.2 and javac in JDK 6 running with the -target 1.4 option.

The solution suggested (from Oracle official documentation)

The most reliably way to produce class files that will work on a particular JDK and later is to compile the source files using the oldest JDK of interest. Barring that, the bootclasspath must be set for robust cross-compilation to an older JDK.

So, this is really not possible?

Spring 4 currently supports Java 6, 7 and 8. Even using Java 7 and Java 8 features and API. How can it then be compatible to Java 7 and 8?!

Spring makes use of source/target and bootclasspath flexibility. Spring 4 always compiles with source/target to Java 6 so that bytecode can still run under JRE 6. Hence no Java 7/8 language features are used: syntax keeps Java 6 level.
But it also uses Java 7 and Java 8 API! Hence, bootclasspath option is not used. Optional, Stream and many other Java 8 API are used. It then injects beans depending on Java 7 or Java 8 API only when JRE 7/8 are detected at runtime: smart approach!

But how does Spring assure API compatibility then?

Using the Maven Animal Sniffer plugin.
This plugin checks whether your application is API compatible with a specified Java version. Is called animal sniffer because Sun traditionally named the different versions of Java after different animals (Java 4 = Merlin (bird), Java 5 = Tiger, Java 6 = Mustang (horse), Java 7 = Dolphin, Java 8 = no animal).

You can add the following to your POM file:

<build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>animal-sniffer-maven-plugin</artifactId>
        <version>1.14</version>
        <configuration>
          <signature>
            <groupId>org.codehaus.mojo.signature</groupId>
            <artifactId>java16</artifactId>
            <version>1.0</version>
          </signature>
        </configuration>
        <executions>
          <execution>
            <id>ensure-java-1.6-class-library</id>
            <phase>verify</phase>
            <goals>
              <goal>check</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
</build>

And the build will fail as soon as you use JDK 7 API while wishing to use only JDK 6 API.

This usage is also recommended in the official source/target page of the maven-compiler-plugin

Note: Merely setting the target option does not guarantee that your code actually runs on a JRE with the specified version. The pitfall is unintended usage of APIs that only exist in later JREs which would make your code fail at runtime with a linkage error. To avoid this issue, you can either configure the compiler's boot classpath to match the target JRE or use the Animal Sniffer Maven Plugin to verify your code doesn't use unintended APIs.


Java 9 Update
In Java 9 this mechanism has been radically changed to the following approach:

javac --release N ...

Will be semantically equivalent to

javac -source N -target N -bootclasspath rtN.jar ...
  • Information about APIs of earlier releases available to javac

    • Stored in a compressed fashion
    • Only provide Java SE N and JDK N-exported APIs that are platform neutral
  • Same set of release values N as for -source / -target
  • Incompatible combinations of options rejected

Main advantages of the --release N approach:

  • No user need to manage artifacts storing old API information
  • Should remove need to use tools like the Maven plugin Animal Sniffer
  • May use newer compilation idioms than the javac in older releases

    • Bug fixes
    • Speed improvements

Update on Java 9 and Maven
Since version 3.6.0, the maven-compiler-plugin provides support for Java 9 via its release option:

The -release argument for the Java compiler, supported since Java9

An example:

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.6.0</version>
    <configuration>
        <release>8</release>
    </configuration>
</plugin>
Tendril answered 10/3, 2016 at 11:34 Comment(7)
Thank you for the detailed explaination. I'll give the mentioned animal plugin a try.Concepcion
A couple small comments on this answer. The -source option controls the language level of the source code accepted by the compiler. The -target option controls the classfile version emitted by the compiler. It may seem like they're independent, but they're really not; the dependency can be quite complicated. For example, compiling a lambda requires the invokedynamic bytecode, so -source 1.8 -target 1.6 simply won't work.Kandacekandahar
The emphasis on library APIs is well on point. Certain language features, such as try-with-resources, require particular library APIs such as AutoCloseable. Thus, -source 1.7 -target 1.6 might produce a valid 1.6 class file, but it won't be runnable on JDK 6. The answer here is quite correct in saying that -source x -target x is insufficient to cross-compile to an earlier JDK version. It's very important to pay attention to the library API issues as well.Kandacekandahar
@StuartMarks thanks for your contribution, really appreciated! I suppose some of these assumptions will change with Java 9 (especially the bootclasspath option referring to the disappearing rt.jar) and its modularizationTendril
Quite correct, there will be changes in Java 9. See also JEP 247 which introduces a new -release option to replace the older options. Not yet final, still subject to change, etc.Kandacekandahar
I might be late to answer, but maven also provide the toolchain : this allow you to use a JDK8 to execute maven and a JDK6 to compile. And in a sense, that way better than using source/target: maven.apache.org/guides/mini/guide-using-toolchains.htmlMusician
Perfect answer. Exactly what I was looking for. Particularly differentiating between source/target, and APIs. If I could I would upvote many times again.Corruption

© 2022 - 2024 — McMap. All rights reserved.