This is how you can setup a Maven project for Java 9

One of the most anticipated events of the Java world is going to be when version 9 gets released on the 21st of September*.  It’ll be a game changer, no doubt about that, primarily due to its module system. It can put an end to the jar-hell we might have been facing for a long time. In this short tutorial, I’d like to show you how a new Java 9 enabled Maven project can be configured.

* The GA  was originally expected to be made available on the 27th of July, however Mark Reinhold has requested another 8 weeks delay in order to go over all the JCP processes.

TL;DR

You can find a sample project here: https://github.com/springuni/springuni-java9.

Modules

We could have created multi module projects in Maven (and also with Gradle, Ant, etc.) in the past as well, however they weren’t modules strictly speaking. Altought they appeared to be separate code bases, there was no standard way to define which functionalities those module provided and required. With build systems you could declare dependencies, but modules themselves didn’t take care of managing their own dependencies and offered services.

In large projects developers quickly experienced the jar-hell when multiple versions of the same library got pulled in as a side effect of transitive dependencies.

The way how Java 9 changes mitigates this is that you can now formally declare modules with module-info.java which encapsulates the following pieces of information.

  • Modules’s name
  • Other modules this module depends on
  • Packages this module provides for other modules

Lots of well known libraries are expected to migrate to Java 9’s module system and as a result they can share their public API and hide their internals with this mechanism.

There’s one caveat however, what are you going to do if you need to use such a library in the future which hasn’t been made available as a module yet? That’s a legitimate question indeed and here come the module types into play.

Module types

Modules are just plain old JAR files as before, but as of Java 9 they will contain a special module-info.java I mentioned above. There’s also a new concept called the module-path, which is a sibling of the well known class-path.

Having that said there are four module types:

  • Named modules (also known as application modules) containing the aforementioned module-info.java
  • Platform modules (similar to the former one, but these are shipped with the JDK)
  • Automatic modules are those old JAR made available on the module path
  • Unnamed module is everything listed on the standard class-path

Implications for Maven users

Build tools like Maven has to handle the new constructs of the JDK, that is, module-info.java and module path. They have a Wiki page which lists all the plugin requirements for Java 9.

Compiler plugin

In order to be able to work with JDK 9’s module system maven-compiler-plugin version 3.6.1 or later is required.

1
2
3
4
5
6
7
8
9
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.6.1</version>
  <configuration>
    <showWarnings>true</showWarnings>
    <showDeprecation>true</showDeprecation>
  </configuration>
</plugin>

Toolchains plugin

This is more or less optional, however I’d strongly recommend to use it. Java 9 hasn’t been released yet, we’re still using Java 8 (or maybe Java 7) for production projects and it’s uncomfortable changing all the environment variables and point them to a JDK 9’s home directory all the time we want to experiment with it. maven-toolchains-plugin enables you to use various environment effortlessly instead.

Create $HOME/.m2/toolchains.xml (or %USERPROFILE%\.m2\toolchains.xml on Windows) if you haven’t had it yet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<toolchains>
  <toolchain>
    <type>jdk</type>
    <provides>
      <version>9</version>
      <vendor>oracle</vendor>
    </provides>
    <configuration>
      <!-- Change path to JDK9 -->
      <jdkHome>/opt/oracle/jdk-9</jdkHome>
    </configuration>
  </toolchain>
  <toolchain>
    <type>jdk</type>
    <provides>
      <version>1.8</version>
      <vendor>oracle</vendor>
    </provides>
    <configuration>
      <jdkHome>/opt/oracle/jdk-1.8.0.65</jdkHome>
    </configuration>
  </toolchain>
</toolchains>

 

Change the path to your actual JDK installation. After that the toolchains plugin can be added to your project.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-toolchains-plugin</artifactId>
  <version>1.1</version>
  <configuration>
    <toolchains>
  	<jdk>
	    <version>9</version>
	    <vendor>oracle</vendor>
  	</jdk>
    </toolchains>
  </configuration>
  <executions>
    <execution>
	  <goals>
	    <goal>toolchain</goal>
  	</goals>
    </execution>
  </executions>
</plugin>

Enable Java 9 language support

As of JDK 9 b72, a new feature became available: the javac --release command line option. In brief, to use javac to cross-compile to an older release of the platform it is not sufficient to just set the -source and -target options to the older value; the bootclasspath must also be set to correspond to the older release too. Setting the bootclasspath was often forgotten and acquiring the needed information could be inconvenient.

The --release flag in javac addresses both of these shortcomings. Only a single flag needs to be set to cross compile compared to three flags (source, -target, -bootclasspath) and the needed information is included in the JDK. The accepted argument values for --release are 6, 7, 8, and 9.

1
2
3
4
5
6
<properties>
  <maven.compiler.release>9</maven.compiler.release>
  <maven.compiler.source>1.9</maven.compiler.source>
  <maven.compiler.target>1.9</maven.compiler.target>
  ...
</properties>

 

Property maven.compiler.release is directly mapped to the --release flag of javac, while the other two properties are only necessary for IntelliJ to understand source compatibility.

Known issues

Unfortunately there are test compilation failures at the time of writing. This is due to a breaking change introduced by JDK-8178012, which removed of the -Xmodule compiler flag. Hopefully MCOMPILER-294 gets fixed soon, in the meantime however, compiling test sources can be disabled.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>${maven-compiler-plugin.version}</version>
  <!--
    Fix breaking change introduced by JDK-8178012: Finish removal of -Xmodule
    Reference:  http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8178012
  -->
  <executions>
    <execution>
      <id>default-testCompile</id>
      <phase>test-compile</phase>
      <goals>
        <goal>testCompile</goal>
      </goals>
      <configuration>
        <skip>true</skip>
      </configuration>
    </execution>
  </executions>
  <configuration>
    <showWarnings>true</showWarnings>
    <showDeprecation>true</showDeprecation>
  </configuration>
</plugin>

 

That’s all folks! 🙂

About the Author László Csontos

I’m László Csontos (@springunidotcom) and my focus area has been Java development in the last 12 years. During past projects I had almost always worked on the back-end. Later I specialized in developing applications with the Spring Framework and got acquainted with its internals. I’d like to share and pass that knowledge on what I learned as a software engineer and help others to boost their carriers by learning Spring which is the most popular Java framework out there for creating enterprise software.

  • Artem

    Big thanks! You saved my day.

    • Hey Artam! I’m glad that it’s helped you. Cheers!