A Maven project that produces Java 17 and Java 8 JAR files
Tags: java, maven
Published: 2022-02-08 11:08:31

## Introduction

Java library authors must choose a minimum version of the Java platform to support. The decision will be a balance between the (possible) size of programmer audience and the author's own development effort.

Put in other words, choosing a higher Java version to support may prevent a number of programmers from trying out the library. The programmers might use a lower Java version at their place of work. On the other hand, choosing a lower Java version limits the Java language features and the JDK API that library authors can use.

At times, however, it might make sense for the library authors to break out from this restriction. Meaning that it might make sense to provide to different users different implementations of the same API. One example is when a newer JDK provides a more performant API.

In this post, I will show you a Maven project that is capable of producing two distinct JAR files of the same API. In a first build pass, a Java 17 JAR file is produced. The classes in this JAR file are compiled and tested with a JDK 17. In a second separate build pass, a Java 8 JAR file is produced. The classes in this JAR file are compiled and tested with a JDK 8. Additionally, while both JAR files provide the same API, the implementation for one of the classes is different.

## Leveraging newer JDK API

Java 9 introduced the [`Arrays.mismatch(byte[], byte[])`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Arrays.html#compare(byte%5B%5D,byte%5B%5D)) method. The method returns the index of the first _mismatch_ between two byte arrays or it returns `-1` if there is no mismatch. If you dig into its implementation, in the `ArraySupport` class you will find [the following Javadocs](https://github.com/openjdk/jdk17u/blob/f18c019287111ce622e6b8140f7061787e11b208/src/java.base/share/classes/jdk/internal/util/ArraysSupport.java#L38):

> The mismatch method implementation, `vectorizedMismatch`, leverages vector-based techniques to access and compare the contents of two arrays. (...) For a byte[] array, 8 bytes (64 bits) can be accessed and compared as a unit rather than individually, which increases the performance when the method is compiled by the HotSpot VM.

Suppose that you, as a library author, needs to compare arrays of bytes between themselves. Suppose also that you have decided that the minimum Java version supported by your library is Java 8. Your library, being a Java 8 project, cannot use the mentioned method as it is not available to you.

Additionally, any programmer, while using your library, will also loose the performance improvement _even_ if they run it on a Java 9+ JVM.

For the latter to not happen, you could provide to different users different implementations of your library depending on the Java platform version your users will be running.

## A quick note on JEP 238

From the library author side of the problem, the preferred solution would be [JEP 238: Multi-Release JAR Files](https://openjdk.java.net/jeps/238).

It has many obvious advantages. The most obvious one, in my opinion, it that it is officially and natively supported by the JDK.

So if you can, I would recommend using it.

This post, however, is not about JEP 238.

## A façade to Arrays.mismatch

To illustrate this post, I have written a simple Java library. It is a façade to the `Arrays.mismatch` method mentioned earlier in this post. In a program, you would use it like so:

```java
byte[] a = // first array
byte[] b = // second array
int result = ByteArrays.mismatch(a, b);
```

Depending on which JAR file you consume, you get different implementations:

- the Java 17 version uses the JDK provided `Arrays.mismatch` method; and
- the Java 8 version uses a simple "compare the arrays one byte a time" solution.

So users invoke the same API, the façade, regardless of the Java version they are running on. However, users on Java 17 (and consuming the Java 17 version of the library) should experience an improved performance as it uses the JDK provided optimized solution.

## Build requirements

To build our example project, the following is required:

- JDK 17;
- JDK 8; and
- Maven 3.

Apart from these required tools, I highly recommend using a working directory for running the example project. The reason being that the build process will download and create many files. So it will be easier to clean up after we finish.

Please note that any non-absolute path name given from this point forward will be relative to this path.

Additionally, the build scripts require that JDKs and Maven be accessible directly from the working directory like so:

```
$ pwd
/home/mendo/objectos/jarfiles

$ ls -l
total 0
lrwxrwxrwx 1 mendo mendo 20 fev  6 21:44 jdk17 -> /opt/jdk/jdk-17.0.2/
lrwxrwxrwx 1 mendo mendo 22 fev  7 08:07 jdk8 -> /opt/jdk/jdk8u322-b06/
lrwxrwxrwx 1 mendo mendo 24 fev  7 08:07 mvn3 -> /opt/apache-maven-3.8.4/

$ jdk17/bin/java -version
openjdk version "17.0.2" 2022-01-18
OpenJDK Runtime Environment (build 17.0.2+8-86)
OpenJDK 64-Bit Server VM (build 17.0.2+8-86, mixed mode, sharing)

$ jdk8/bin/java -version
openjdk version "1.8.0_322"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_322-b06)
OpenJDK 64-Bit Server VM (Temurin)(build 25.322-b06, mixed mode)

$ JAVA_HOME=jdk17/ mvn3/bin/mvn --version
Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537)
Maven home: /home/mendo/objectos/jarfiles/mvn3
Java version: 17.0.2, vendor: Oracle Corporation, runtime: /opt/jdk/jdk-17.0.2
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "5.15.11-gentoo", arch: "amd64", family: "unix"
```

This should be enough to build the example in this post.

## Downloading the example

You can browse the source code for this example [here](https://www.objectos.com.br/blog/maven-project-that-produces-java17-and-java8-jar-files/src.html).

Let's get all of the required files for our build:

```
$ wget --cut-dirs=2 \
       --no-host-directories \
       --no-parent \
       --recursive \
       --reject "src.html*" \
       https://www.objectos.com.br/blog/maven-project-that-produces-java17-and-java8-jar-files/src.html
```

And let's verify that we got all of the required files:

```
$ find bin/ library/ -type f | sort
bin/mvn17
bin/mvn8
library/pom.xml
library/src/main/java/library/ByteArrays.java
library/src/main/java/library/Mismatch.java
library/src/main/java17/library/MismatchJava17.java
library/src/main/java17/module-info.java
library/src/main/java8/library/MismatchJava8.java
library/src/test/java/library/ByteArraysTest.java
library/toolchains.xml
```

Let's ensure there are no `rm -rf /` or similar in the downloaded scripts:

```
$ cat bin/* | grep --invert-match ^# | grep .
mvn3/bin/mvn \
    --activate-profiles java17 \
    --define maven.repo.local=m2/repo-java17 \
    --file library/pom.xml \
    --no-transfer-progress \
    --toolchains library/toolchains.xml \
    "$@"
mvn3/bin/mvn \
    --activate-profiles java8 \
    --define maven.repo.local=m2/repo-java8 \
    --file library/pom.xml \
    --no-transfer-progress \
    --toolchains library/toolchains.xml \
    "$@"
```

Seems all right. Finally, let's make the scripts executable:

```
$ chmod +x bin/*
```

## A quick note on ByteArrays and MismatchSingleton

The 'entry point' of our library is the `ByteArrays` class. If you look at the source code of the `ByteArrays` class, you will notice it references a non-existent `MismatchSingleton` type:

```java
package library;

public final class ByteArrays {
  private ByteArrays() {}

  public static int mismatch(byte[] a, byte[] b) {
    return MismatchSingleton.INSTANCE.mismatch(a, b);
  }
}
```

The source code for the `MismatchSingleton` will be generated at compilation time by an annotation processor from [Objectos Latest](https://www.objectos.com.br/software/next/java17/api/br.com.objectos.latest/module-summary.html).

## Building the Java 17 JAR file

Now that we have our build scripts setup, we can proceed and build our project.

Let's start with the Java 17 version.

Before we do, please note that:

- a custom Maven local repository will be used. Therefore, Maven will download all of the artifacts from all of the plugins; and
- while downloading, Maven will not echo download logging messages as it will be invoked with the `--no-transfer-progress` option.

OK, let's build by invoking the `verify` phase.

```
$ bin/mvn17 verify
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
```

The example project is a simple JAR Maven project having the following attributes:

- groupId: br.com.objectos.www
- artifactId: library
- version: 1-SNAPSHOT

It takes some time as Maven has to download all of artifacts for all of the plugins.

```
[INFO] --- maven-toolchains-plugin:3.0.0:toolchain (toolchains-jdk) @ library ---
[INFO] Required toolchain: jdk [ version='17' ]
[INFO] Found matching toolchain for type jdk: JDK[jdk17/]
```

The first plugin in the execution is the [Maven Toolchains Plugin](https://maven.apache.org/plugins/maven-toolchains-plugin/). It says it requires a JDK toolchain with the `version='17'` condition. In the following line it finds the JDK 17 toolchain in the working directory.

```
[INFO] --- build-helper-maven-plugin:3.3.0:add-source (add-source) @ library ---
[INFO] Source directory: /home/mendo/objectos-jarfiles/library/src/main/java17 added.
[INFO] Source directory: /home/mendo/objectos-jarfiles/library/src/main/java8 added.
```

The next plugin in the execution is the [Build Helper Maven Plugin](https://www.mojohaus.org/build-helper-maven-plugin/index.html). We can see that it adds two source directories to the build process:

- src/main/java17
- src/main/java8

```
[INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ library ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] skip non existing resourceDirectory /home/mendo/objectos/jarfiles/library/src/main/resources
```

Then the [Maven Resources Plugin](https://maven.apache.org/plugins/maven-resources-plugin/) does its default execution.

```
[INFO] --- maven-compiler-plugin:3.9.0:compile (default-compile) @ library ---
[INFO] Toolchain in maven-compiler-plugin: JDK[jdk17/]
[INFO] Compiling 5 source files to /home/mendo/objectos-jarfiles/library/target-java17/classes
```

Now the [Maven Compiler Plugin](https://maven.apache.org/plugins/maven-compiler-plugin/). We can see that the plugin:

- uses the JDK 17 toolchain selected by the Maven Toolchains Plugin earlier;
- compiles 5 source files; and
- writes the class files to the target-java17/classes directory.

```
[INFO] --- build-helper-maven-plugin:3.3.0:add-test-source (add-test-source) @ library ---
[INFO] Test Source directory: /home/mendo/objectos/jarfiles/library/src/test/java17 added.
[INFO] Test Source directory: /home/mendo/objectos/jarfiles/library/src/test/java8 added.
[INFO]
[INFO] --- maven-resources-plugin:3.2.0:testResources (default-testResources) @ library ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Using 'UTF-8' encoding to copy filtered properties files.
[INFO] skip non existing resourceDirectory /home/mendo/objectos/jarfiles/library/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.9.0:testCompile (default-testCompile) @ library ---
[INFO] Toolchain in maven-compiler-plugin: JDK[jdk17/]
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/mendo/objectos/jarfiles/library/target-java17/test-classes
```

Next the Build Helper Maven Plugin, the Maven Resources Plugin and the Maven Compiler Plugin do the same steps as before but for the tests.

```
[INFO] --- maven-surefire-plugin:3.0.0-M5:test (default-test) @ library ---
[INFO] Toolchain in maven-surefire-plugin: JDK[jdk17/]
```

Later in the process, we can see that the [Maven Surefire Plugin](https://maven.apache.org/surefire/maven-surefire-plugin/), like the Maven Compiler Plugin before, is also using the JDK 17 toolchain for running the tests.

```
[INFO] Results:
[INFO]
[INFO] Tests run: 64, Failures: 0, Errors: 0, Skipped: 0
```

OK, all tests green.

```
[INFO] --- maven-jar-plugin:3.2.2:jar (default-jar) @ library ---
[INFO] Building jar: /home/mendo/objectos-jarfiles/library/target-java17/library-1-SNAPSHOT.jar
```

Finally, we can see that the [Maven JAR Plugin](https://maven.apache.org/plugins/maven-jar-plugin/) writes the JAR file to the `target-java17` directory.

```
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:09 min
[INFO] Finished at: 2022-02-07T08:30:18-03:00
[INFO] ------------------------------------------------------------------------
```

OK, it seems that everything was compiled and tested with the JDK 17 in our working directory.

Let's confirm by checking one of the compiled classes:

```
$ jdk17/bin/javap \
    -verbose \
    library/target-java17/classes/library/ByteArrays.class \
    | grep major
  major version: 61
```

Major version is 61 which, according to [this table](https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-4.html#jvms-4.1-200-B.2), is the correct value for Java 17.

Let's see the generated source code:

```
$ cat library/target-java17/generated-sources/annotations/library/*
package library;

import br.com.objectos.latest.Generated;

@Generated("br.com.objectos.latest.processor.SingletonProcessor")
final class MismatchSingleton {
  static final library.MismatchJava17 INSTANCE = MismatchJava17.INSTANCE;

  private MismatchSingleton() {}
}
```

The `MismatchSingleton` is very simple and references the `MismatchJava17` type.

Finally let's see the classes on the Maven generated JAR file:

```
$ jdk17/bin/jar \
    --file library/target-java17/library-1-SNAPSHOT.jar \
    --list \
    --verbose \
    | grep \.class$
   836 Sun Feb 06 11:02:44 BRT 2022 library/MismatchJava8.class
   437 Sun Feb 06 11:02:44 BRT 2022 library/MismatchSingleton.class
   497 Sun Feb 06 11:02:44 BRT 2022 library/ByteArrays.class
   525 Sun Feb 06 11:02:44 BRT 2022 library/MismatchJava17.class
   294 Sun Feb 06 11:02:44 BRT 2022 library/Mismatch.class
   246 Sun Feb 06 11:03:02 BRT 2022 module-info.class
```

We can see it contains the compiled class of the generated `MismatchSingleton`. It also contains a compiled `module-info.class` class file.

## Building the Java 8 JAR file

Next, let's build the Java 8 version by invoking the verify phase.

```
$ bin/mvn8 verify
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
```

OK, the same coordinates as the Java 17 version.

```
[INFO] --- maven-toolchains-plugin:3.0.0:toolchain (toolchains-jdk) @ library ---
[INFO] Required toolchain: jdk [ version='1.8' ]
[INFO] Found matching toolchain for type jdk: JDK[jdk8/]
```

This time, we see that the [Maven Toolchains Plugin](https://maven.apache.org/plugins/maven-toolchains-plugin/) requires a JDK toolchain with the `version='1.8'` condition. It finds and selects the JDK 8 in the working directory for the build.

```
[INFO] --- build-helper-maven-plugin:3.3.0:add-source (add-source) @ library ---
[INFO] Source directory: /home/mendo/objectos-jarfiles/library/src/main/java8 added.
```

The [Build Helper Maven Plugin](https://www.mojohaus.org/build-helper-maven-plugin/index.html) only adds a single source set to the build: src/main/java8.

```
[INFO] --- maven-compiler-plugin:3.9.0:compile (default-compile) @ library ---
[INFO] Toolchain in maven-compiler-plugin: JDK[jdk8/]
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 3 source files to /home/mendo/objectos-jarfiles/library/target-java8/classes
```

The [Maven Compiler Plugin](https://maven.apache.org/plugins/maven-compiler-plugin/) uses JDK 8 and compiles only 3 source files. This is different from the 5 source files compiled in the Java 17 version.

```
[INFO] --- maven-surefire-plugin:3.0.0-M5:test (default-test) @ library ---
[INFO] Toolchain in maven-surefire-plugin: JDK[jdk8/]
```

The [Maven Surefire Plugin](https://maven.apache.org/surefire/maven-surefire-plugin/) uses JDK 8.

```
[INFO] Results:
[INFO]
[INFO] Tests run: 64, Failures: 0, Errors: 0, Skipped: 0
```

Like the Java 17 version, all tests are green.

The important thing to keep in mind here is that the source code of the tests in this Java 8 build pass is _exactly the same_ as the source code of the tests in the Java 17 build pass.

```
[INFO] --- maven-jar-plugin:3.2.2:jar (default-jar) @ library ---
[INFO] Building jar: /home/mendo/objectos-jarfiles/library/target-java8/library-1-SNAPSHOT.jar
```

And the [Maven JAR Plugin](https://maven.apache.org/plugins/maven-jar-plugin/) writes the JAR file to the `target-java8` directory:

Like we did in the Java 17 section, let's check the ByteArrays compiled class file:

```
$ jdk17/bin/javap \
    -verbose \
    library/target-java8/classes/library/ByteArrays.class \
    | grep major
  major version: 52
```

OK, major version is 52, the correct value for Java 8.

Let's see the generated source code:

```
$ cat library/target-java8/generated-sources/annotations/library/*
package library;

import br.com.objectos.latest.Generated;

@Generated("br.com.objectos.latest.processor.SingletonProcessor")
final class MismatchSingleton {
  static final library.MismatchJava8 INSTANCE = MismatchJava8.INSTANCE;

  private MismatchSingleton() {}
}
```

The `MismatchSingleton` source code is almost identical to the Java 17 version, except it references a `MismatchJava8` type.

Finally let's see the classes on the Maven generated JAR file:

```
$ jdk17/bin/jar \
    --file library/target-java8/library-1-SNAPSHOT.jar \
    --list \
    --verbose \
    | grep \.class$
   836 Sun Feb 06 11:46:30 BRT 2022 library/MismatchJava8.class
   435 Sun Feb 06 11:46:30 BRT 2022 library/MismatchSingleton.class
   495 Sun Feb 06 11:46:30 BRT 2022 library/ByteArrays.class
   294 Sun Feb 06 11:46:30 BRT 2022 library/Mismatch.class
```

Like the Java 17 version, it contains the compiled class of the `MismatchSingleton` type. Unlike the Java 17 version, it does not include the `module-info.class` file.

## Conclusion

In this post I showed you a working Maven project capable of producing a Java 17 and a Java 8 JAR files. The Java 17 JAR file is built and tested with JDK 17. The Java 8 JAR file is built and tested with JDK 8.

They both provide the same API. We verify this fact by running the same test suite for both versions of the library.

They provide different implementations of the same API. We verify this fact by inspecting the generated source file of the `MismatchSingleton` class.

## Next steps

In a number of upcoming posts I will explain how to incrementally change a vanilla Java 17 Maven project so it reaches the final form shown in this post.

This way I hope to explain in details how the Maven project shown in this post really works.

Thank you for reading and until the next post.

_Originally published at the [Objectos Software Blog](https://www.objectos.com.br/blog/maven-project-that-produces-java17-and-java8-jar-files.html) on February 7<sup>th</sup>, 2022._

Follow me on [twitter](https://twitter.com/marcioendo).