How I write the Objectos Libraries, Part 1

Marcio Endo
January 28, 2022

edit 2022-05-31: please note that this post no longer represent my process for writing the Objectos libraries. It is only kept here for historical reasons.

Introduction

I have recently released version 3.1.0^^1^^ of the Objectos suite of Java libraries. If you search the Maven central repository for the artifact ID of one of the libraries, say objectos-git, you will notice that it returns a number of different group IDs. At time of writing the group IDs are:

Each group ID targets a different Java version. This is indicated by the last part of the group ID's name. So to use an Objectos library in your program, you select the group ID that better matches the runtime in which the program will run. For example, if you have a Java 11^^2^^ runtime you declare the dependency like so:

<dependency>
  <groupId>br.com.objectos.oss-java11</groupId>
  <artifactId>objectos-git</artifactId>
  <version>3.1.0</version>
</dependency>

Although each group ID targets a different Java version, all artifacts of all of the group IDs have the following in common:

On the other hand, in addition to targeting different Java versions, different group IDs have the following additional differences:

To achieve this, I use a combination of stock tools:

And tools I developed:

In this series I will give you an in-depth look on the tools, the setup and the processes I use to develop and release the Objectos Java libraries. I have been using it since the release of Objectos 1.0.0 on July 2021. It allows me, as a solo Java library developer, to develop, test and release the Objectos suite of Java libraries while keeping the overhead tolerable.

Our running example

To illustrate this series, we will design, develop, test, build and release^^5^^ a simple Java library. We will produce two different JAR files of the same library; one will target Java 17 and the second one will target Java 8.

The library does not need to do something particularly useful, it just needs to serve as a good example. In this case in particular it will do somethig useful. In fact, it is something on the roadmap of the Objectos Core Array library.

Comparing two arrays of bytes

I was recently reading the javadocs for the SSLEngine class. There, in a code example, it checks if a string value coming from the network is equal to some expected value. It does so by first decoding the string into a array of bytes and then comparing the bytes to an expected byte array value. To compare the arrays, it uses a method I did not know existed, the Arrays.compare(byte[], byte[]) method.

Reading the javadocs for the Arrays.compare method, we can see that it was introduced in Java 9 and that it has some relation to the Arrays.mismatch(byte[], byte[]) method. In fact, reading the source code of the former method, we can see that it makes use of the internal ArraySupport.mismatch method which, in turn, uses the internal ArraySupport.vectorizedMismatch method (when the array length is greater than 7). From the ArraySupport javadocs:

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.

If I am not mistaken, I compare arrays of bytes in the Objectos Git library. Except I do not use the optimized Arrays.mismatch method. Instead, I do the naive solution of comparing the arrays one byte at a time by looping through the values of the array.

A wrapper around Arrays.mismatch(byte[], byte[])

If I plan for the Objectos Git library to be compatible with Java 6, 7 or 8, I cannot use the Arrays.mismatch method as it was only added in Java 9. However, If I do not use it, users on Java 9+ will miss a performance improvement^^6^^.

The solution that Objectos provides in this case is to introduce an indirection. Users on all Java versions call another method say ByteArrays.mismatch:

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

Somehow (this is what this post will discuss) there will be two different implementations for the method:

The Required tools

If you do not wish to code along, you can probably skip to the next section. Otherwise, please read on.

To try and get some degree of reproducibility for the examples in this post (and in this series), we will use the same versions of the same tools. In other words, I am pinning the versions of the tools we will use for this post.

This is, of course, optional; you can use the tools already available on your machine. However, all of the shell listings use the paths for the downloaded tools.

Also I should note I am on an amd64 Linux machine:

$ uname --all
Linux eto001 5.10.76-gentoo-r1 #1 SMP PREEMPT Sun Nov 21 17:09:16 -03 2021 x86_64 AMD Phenom(tm) II X4 955 Processor AuthenticAMD GNU/Linux

I did not try this on a Windows machine nor on a Mac machine. That is the reason I emphasized the some degree of reproducibility earlier. I am sorry for that.

Creating a working directory

Let's first create a working directory for our project:

$ mkdir o7
$ cd o7
$ pwd
/home/mendo/o7

Any non-absolute path name given from this point forward will be relative to this path.

All files will go into this directory, including those downloaded or generated by tools (hopefully). It will be easier to clean up after ourselves after we finish.

Getting and installing

From the last part of the previous section, we decided on building two different implementations for our library. One implementation for Java 17 and another for Java 8. Therefore, we will need two different JDKs. We will also need Maven 3. The exact versions are:

Let's get them all at once:

$ wget https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-x64_bin.tar.gz \
  https://corretto.aws/downloads/resources/8.322.06.2/amazon-corretto-8.322.06.2-linux-x64.tar.gz \
  https://dlcdn.apache.org/maven/maven-3/3.8.4/binaries/apache-maven-3.8.4-bin.tar.gz

We now extract the archives and create some symlinks for easier access:

$ tar xzf openjdk-17.0.2_linux-x64_bin.tar.gz
$ tar xzf amazon-corretto-8.322.06.2-linux-x64.tar.gz
$ tar xzf apache-maven-3.8.4-bin.tar.gz
$ ln -s jdk-17.0.2/ jdk17
$ ln -s amazon-corretto-8.322.06.2-linux-x64/ jdk8
$ ln -s apache-maven-3.8.4/ mvn3

And we check everything:

$ 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 Corretto-8.322.06.2 (build 1.8.0_322-b06)
OpenJDK 64-Bit Server VM Corretto-8.322.06.2 (build 25.322-b06, mixed mode)

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

We have all of the required tools checked. We can now remove the downloaded files:

$ rm openjdk*.tar.gz amazon*.tar.gz apache*.tar.gz
$ ls -l
total 12
drwxr-xr-x 7 mendo mendo 4096 jan 18 17:23 amazon-corretto-8.322.06.2-linux-x64
drwxr-xr-x 6 mendo mendo 4096 jan 20 09:15 apache-maven-3.8.4
drwxr-xr-x 8 mendo mendo 4096 jan 20 09:17 jdk-17.0.2
lrwxrwxrwx 1 mendo mendo   11 jan 20 09:18 jdk17 -> jdk-17.0.2/
lrwxrwxrwx 1 mendo mendo   37 jan 20 09:18 jdk8 -> amazon-corretto-8.322.06.2-linux-x64/
lrwxrwxrwx 1 mendo mendo   19 jan 20 09:18 mvn3 -> apache-maven-3.8.4/

Java 17 library implementation

In this section we will design, write, test and build an implementation of our library. This first tentative implementation will target the highest Java version we want to support, Java 17. We will use Maven as our build tool. Let's start with the library's pom.xml file.

For those who wish to code along, please know that we will download all of the required files later in this section.

The pom.xml file

If you wish to follow along, get the file here.

The first thing we are going to do is to instruct the maven-compiler-plugin that this is a Java 17 project. We will set, at the POM project level, the plugin's properties to the desired values:

<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
</properties>

Please note that, depending on your requirements, the maven.compiler.release property is probably a better and more compact solution. However, we will also build this library using a Java 8 compiler in a later step and this option was only introduced in JDK 9.

On the coding side, we will start by writing test cases. So we will add dependencies for testing.

I am used to using TestNG, so will add that. Also, since we will be dealing with byte arrays, we can use Objectos Testing Random for generating random byte array values. This is a Java 17 project so we will use the oss-java17 group ID for the Objectos testing library. We will also set convenience properties for the versions of dependencies. The versions will be set to the latest at time of writing.

<properties>
    <objectos.groupId>br.com.objectos.oss-java17</objectos.groupId>
    <objectos.version>3.1.0</objectos.version>

    <testng.version>7.5</testng.version>
</properties>
...
<dependencies>
    <dependency>
        <groupId>${objectos.groupId}</groupId>
        <artifactId>objectos-testing-random</artifactId>
        <version>${objectos.version}</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>${testng.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Finally, to ensure some degree of reproducibility, we will set the character encoding and will set and pin the version for each of the standard Maven plugins. As with the dependencies, the versions of the plugins will be set to the latest at time of writing. I usually refer to this page for the latest versions.

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<build>
    <pluginManagement>
        <plugins>
            ...
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.9.0</version>
            </plugin>
            ...
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M5</version>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

Next we will design our tests.

Designing our tests

We will use the javadocs for the Arrays.mismatch(byte[], byte[]) method as our specification. It states:

Finds and returns the index of the first mismatch between two byte arrays, otherwise return -1 if no mismatch is found. The index will be in the range of 0 (inclusive) up to the length (inclusive) of the smaller array.

From the otherwise sentence above follows that it should return -1 if the arrays are equal to each other. Let's note it in a quasi code:

test(-1, arr(), arr());
test(-1, arr(1), arr(1));
test(-1, arr(1, 2), arr(1, 2));
test(-1, arr(1, 2, 3), arr(1, 2, 3));

The first argument of test method invocation is the expected result of our method. The remaining arguments are the byte array values. They will be created by some method named arr which accept int values.

The last sentence gives the range of the possible return values: from zero to the length of the smaller array. It is "the index of the first mismatch between two byte arrays". So it should return zero when the first element of the larger array is the first mismatch:

test(0, arr(), arr(1));
test(0, arr(), arr(1, 2));
test(0, arr(1), arr());
test(0, arr(1, 2), arr());
test(0, arr(3), arr(5));
test(0, arr(3), arr(5, 6));
test(0, arr(3, 4), arr(5, 6));
test(0, arr(3, 4, 5), arr(5, 6));

And it should return 1 when the second element of the larger array is the first mismatch:

test(1, arr(1, 2), arr(1));
test(1, arr(1, 2, 3), arr(1));
test(1, arr(9), arr(9, 8, 7));

And it should return 2 when the third element of the larger array is the first mismatch:

test(2, arr(1, 2, 3), arr(1, 2));
test(2, arr(1, 2, 3, 4), arr(1, 2));
test(2, arr(1, 2, 3, 4), arr(1, 2, 9));
test(2, arr(9, 8), arr(9, 8, 7));

And so on.

As we saw in an earlier section, the optimized algorithm is only used for arrays lengths greater than 7. We will add a parameterized test for this. The parameter is the array length.

With the length len we create a random byte array and we immediately create a copy of the same length. We proceed and test that there is no mismatch. Again in quasi code:

a = random(len);
b = copyOf(a, len);

test(-1, a, a);
test(-1, a, b);
test(-1, b, a);

We then create a larger copy of the original array. We check that the mismatch is equal to the smaller array length:

b = copyOf(a, len + 1);

test(len, a, b);
test(len, b, a);

We then force the first element of the copied array to be different and check that the mismatch is at index zero:

b[0] = a[0] + 1;

test(0, a, b);
test(0, b, a);

We then move the mismatch to the next index. To simplify, we will do it only for the last element of the smaller array:

b[0] = a[0];
b[len - 1] = a[len - 1] + 1;

test(len - 1, a, b);
test(len - 1, b, a);

And this should be enough for now.

ByteArraysTest.java

The full version of the file is here.

To keep the overhead of maintaining multiple builds of the same library tolerable, we will, ideally, use the same tests for both builds of our library. Therefore, in our tests, we must only use Java 8 language features and API. Since our tests will be quite simple, this is almost a non-issue in this case. But it is something important to keep in mind.

To write the ByteArraysTest.java file we have to translate the quasi code snippets from the previous section to actual Java code.

To simplify the directory hierarchy, we will name the library's package simply library.

The test method must call our library's method and verify the result:

private void test(int expected, byte[] a, byte[] b) {
  int result;
  result = ByteArrays.mismatch(a, b);

  Assert.assertEquals(result, expected);
}

The arr method simply creates a byte array from an int array by casting the values:

private byte[] arr(int... values) {
  byte[] arr;
  arr = new byte[values.length];

  for (int i = 0; i < arr.length; i++) {
    arr[i] = (byte) values[i];
  }

  return arr;
}

For the parameterized test, we will use a TestNG @DataProvider annotated method. It will provide lengths from 1 to 64:

@DataProvider
public Object[][] mismatchParam() {
  Object[][] result;
  result = new Object[63][];

  for (int i = 0; i < result.length; i++) {
    result[i] = new Object[] {i + 1};
  }

  return result;
}

Finally, the random and the copyOf methods will be respectively implemented using:

ByteArrays.java

The full version of the file is here.

Of course, if we try and build our library it would fail as there is no implementation for the ByteArrays class.

For this particular implementation, we will just invoke to the Arrays.mismatch method:

package library;

import java.util.Arrays;

public final class ByteArrays {
  private ByteArrays() {}

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

module-info.java

The full version of the file is here.

Let's make the library modular by creating a module-info.java file:

module library {
  exports library;
}

Getting all of the files

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

$ wget --cut-dirs=3 \
     --no-host-directories \
     --no-parent \
     --recursive \
     --reject "index.html*" \
     https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v1/index.html

Verify that we got all of the files:

$ find projects/ -type f
projects/library/src/main/java/module-info.java
projects/library/src/main/java/library/ByteArrays.java
projects/library/src/test/java/library/ByteArraysTest.java
projects/library/pom.xml

Maven local repository

Before we actually run Maven to build and test our library, let's first create an alternate local repository. The local repository is the place where Maven stores all the artifacts it downloads.

This is, of course, optional. But it will allow cleaning up the Maven downloaded artifacts after we finish.

To instruct Maven to use our custom local repository, we will set the maven.repo.local property from the command line. You can also set the local repository through the settings.xml file.

Let's create the directory:

$ mkdir --parents m2/repository

Maven build

Now we can run Maven using our alternate local repository:

$ JAVA_HOME=jdk17/ mvn3/bin/mvn \
  --define maven.repo.local=m2/repository \
  --file projects/library/pom.xml \
  install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from rio-repository: http://rio/nexus/content/groups/public/org/apache/maven/plugins/maven-resources-plugin/3.2.0/maven-resources-plugin-3.2.0.pom
Downloaded from rio-repository: http://rio/nexus/content/groups/public/org/apache/maven/plugins/maven-resources-plugin/3.2.0/maven-resources-plugin-3.2.0.pom (8.1 kB at 34 kB/s)
(...)
[INFO]
[INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ library ---
Downloading from rio-repository: http://rio/nexus/content/groups/public/org/apache/maven/maven-plugin-api/3.1.0/maven-plugin-api-3.1.0.pom
Downloaded from rio-repository: http://rio/nexus/content/groups/public/org/apache/maven/maven-plugin-api/3.1.0/maven-plugin-api-3.1.0.pom (3.0 kB at 22 kB/s)
(...)
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running library.ByteArraysTest
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[INFO] Tests run: 64, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.535 s - in library.ByteArraysTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 64, Failures: 0, Errors: 0, Skipped: 0
(...)
[INFO] --- maven-install-plugin:3.0.0-M1:install (default-install) @ library ---
Downloading from rio-repository: http://rio/nexus/content/groups/public/org/codehaus/plexus/plexus-component-annotations/1.7.1/plexus-component-annotations-1.7.1.pom
Downloaded from rio-repository: http://rio/nexus/content/groups/public/org/codehaus/plexus/plexus-component-annotations/1.7.1/plexus-component-annotations-1.7.1.pom (770 B at 5.4 kB/s)
(...)
[INFO] Installing /home/mendo/o7/projects/library/target/library-1-SNAPSHOT.jar to /home/mendo/o7/m2/repository/br/com/objectos/www/library/1-SNAPSHOT/library-1-SNAPSHOT.jar
[INFO] Installing /home/mendo/o7/projects/library/pom.xml to /home/mendo/o7/m2/repository/br/com/objectos/www/library/1-SNAPSHOT/library-1-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:05 min
[INFO] Finished at: 2022-01-24T11:55:47-03:00
[INFO] ------------------------------------------------------------------------

The ellipsis (...) indicate suppressed lines from the output.

The messages from the maven-install-plugin^^8^^ indicate that Maven picked up the local repository we configured. In any case, let's check our local repository:

$ find m2/repository/ -type f
m2/repository/javax/inject/javax.inject/1/javax.inject-1.pom
m2/repository/javax/inject/javax.inject/1/javax.inject-1.pom.sha1
m2/repository/javax/inject/javax.inject/1/javax.inject-1.jar.sha1
m2/repository/javax/inject/javax.inject/1/_remote.repositories
m2/repository/javax/inject/javax.inject/1/javax.inject-1.jar
(...)
m2/repository/br/com/objectos/oss-java17/objectos-core-object/3.1.0/objectos-core-object-3.1.0.pom.sha1
m2/repository/br/com/objectos/oss-java17/objectos-core-object/3.1.0/objectos-core-object-3.1.0.jar
m2/repository/br/com/objectos/oss-java17/objectos-core-object/3.1.0/objectos-core-object-3.1.0.pom
m2/repository/br/com/objectos/oss-java17/objectos-core-object/3.1.0/_remote.repositories
m2/repository/br/com/objectos/oss-java17/objectos-core-object/3.1.0/objectos-core-object-3.1.0.jar.sha1

OK, the downloaded artifacts were stored in the local repository.

Java 8 library implementation

So far we have successfully created, tested and built the Java 17 version of our library. If we try to use in a Java 8 application it will fail as:

To illustrate the last point, let's try to build our library with JDK 8. First, we create a copy of the original project just so we can safely remove the module-info.java file:

$ cp --archive projects/library library-java8
$ rm library-java8/src/main/java/module-info.java
$ JAVA_HOME=jdk8/ mvn3/bin/mvn \
    --define maven.compiler.target=1.8 \
    --define maven.compiler.source=1.8 \
    --define maven.repo.local=m2/repository \
    --file library-java8/pom.xml \
    clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] --- maven-compiler-plugin:3.9.0:compile (default-compile) @ library ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/mendo/o7/library-java8/target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /home/mendo/o7/library-java8/src/main/java/library/ByteArrays.java:[9,18] cannot find symbol
  symbol:   method mismatch(byte[],byte[])
  location: class java.util.Arrays
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.876 s
[INFO] Finished at: 2022-01-24T12:48:47-03:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.9.0:compile (default-compile) on project library: Compilation failure
[ERROR] /home/mendo/o7/library-java8/src/main/java/library/ByteArrays.java:[9,18] cannot find symbol
[ERROR]   symbol:   method mismatch(byte[],byte[])
[ERROR]   location: class java.util.Arrays
[ERROR]
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

As expected, the build fails with a compilation error as the Arrays.mismatch method does not exist in Java 8. We can cleanup the copy we made:

$ rm --recursive library-java8/

Problems to solve for Java 8

So what we want now is to be able to, from the same project, build two different artifacts: one that can be used in a Java 17 application and another that can be used in a Java 8 application. For this, there are a number of problems we need to solve.

First, from a ease of use point of view, the selection of which version to build should be, ideally, some flag or property that can be easily set from the command line:

# Builds the java17 version"
mvn 'some flag for java17' install

# Builds the java8 version"
mvn 'some flag for java8' install

Second, as we want to have a single project that can produce two different artifacts, we need a way to "hide" the Java 17 specific source code from the Java 8 compiler.

Third, our ByteArrays.mismatch method is a static one and both versions of the library must provide the same method signature. How can we provide different implementations to a static method while keeping the signature the same?

The solutions I am currently using are, in order:

  1. Maven profiles;

  2. the build-helper-maven-plugin; and

  3. dispatch to a singleton.

Maven profiles can solve our first problem as they can be easily activated from the command line using the option --activate-profiles (or with its short form -P).

The build-helper-maven-plugin allows for adding more source directories to a build execution. So a Java 17 build can "see" more source directories than the Java 8 one.

Finally, a singleton could be used as follows:

// public API
public class ByteArrays {
  public static int mismatch(byte[] a, byte[] b) {
    return MismatchSingleton.INSTANCE.mismatch(a, b);
  }
}

// the java file will be generated at compile time.
final class MismatchSingleton {
  static final Mismatch INSTANCE = // MismatchJava8 or 17.INSTANCE;
}

// the java file resides at src/main/java
abstract class Mismatch {
  abstract int mismatch(byte[] a, byte[] b);
}

// the java file resides at src/main/java8
final class MismatchJava8 extends Mismatch {
  static final Mismatch INSTANCE = new MismatchJava8();
  final int mismatch(byte[] a, byte[] b) {
    // java 8 impl...
  }
}

// the java file resides at src/main/java17
final class MismatchJava17 extends Mismatch {
  static final Mismatch INSTANCE = new MismatchJava17();
  final int mismatch(byte[] a, byte[] b) {
    // java 17 impl...
  }
}

In the Java 8 build, the compiler will only "see" the Java 8 implementation and the MismatchSingleton java file will be generated as:

final class MismatchSingleton {
  static final Mismatch INSTANCE = MismatchJava8.INSTANCE;
}

In the Java 17 build, the compiler will see all of the implementations and the MismatchSingleton java file will be generated as:

final class MismatchSingleton {
  static final Mismatch INSTANCE = MismatchJava17.INSTANCE;
}

This requires code generation which is done at compile time by an annotation processor provided by the Objectos Latest library.

Refactoring our library: adding Maven profiles

So, in order to support the creation of an additional binary artifact, the one for Java 8, we will modify our project and introduce two Maven profiles. Each profile will target a different Java version.

In this first iteration, we will just move some of the properties from the POM project level to the POM project profile level. In particular, we will move:

  1. the maven-compiler-plugin configuration properties; and

  2. the Objectos group ID property.

Like so:

<profiles>
    <profile>
        <id>java17</id>
        <properties>
            <maven.compiler.source>17</maven.compiler.source>
            <maven.compiler.target>17</maven.compiler.target>

            <objectos.groupId>br.com.objectos.oss-java17</objectos.groupId>
        </properties>
    </profile>
    <profile>
        <id>java8</id>
        <properties>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>

            <objectos.groupId>br.com.objectos.oss-java8</objectos.groupId>
        </properties>
    </profile>
</profiles>

You can find the full version of the file here.

Let's download the new pom.xml file build the project with the Java 17 profile we have just created:

$ rm --recursive projects/
$ wget --cut-dirs=3 \
       --no-host-directories \
       --no-parent \
       --recursive \
       --reject "index.html*" \
       https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v2/index.html
$ JAVA_HOME=jdk17/ mvn3/bin/mvn \
    --activate-profiles java17 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 2-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  7.227 s
[INFO] Finished at: 2022-01-24T15:18:59-03:00
[INFO] ------------------------------------------------------------------------

OK, the Java 17 build was successful.

The Java 8 profile should fail as we have not changed any of the source files. So we will not run it. But let's do a Maven run without a profile and assert that it fails. It should fail as one of the dependencies, namely objectos-testing-random will not have a groupId defined when no profiles are active (remember we just moved the group ID property to our profiles):

$ JAVA_HOME=jdk17/ mvn3/bin/mvn \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    clean install
[INFO] Scanning for projects...
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[ERROR] 'dependencies.dependency.groupId' for ${objectos.groupId}:objectos-testing-random:jar with value '${objectos.groupId}' does not match a valid id pattern. @ line 89, column 13
 @
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]
[ERROR]   The project br.com.objectos.www:library:2-SNAPSHOT (/home/mendo/o7/projects/library/pom.xml) has 1 error
[ERROR]     'dependencies.dependency.groupId' for ${objectos.groupId}:objectos-testing-random:jar with value '${objectos.groupId}' does not match a valid id pattern. @ line 89, column 13
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException

In a proper project it is probably a bad idea for the build to fail if no profiles are activated. But this error confirms that the Maven profile we have just created is indeed working.

Refactoring our library: adding the build-helper-maven-plugin

So far, by activating different profiles, we can instruct Maven to compile our Java files in two different ways, one targeting Java 17 and the other targeting Java 8. We still cannot actually compile in the latter way as the source code makes use of API not available in JDK 8.

To solve this, we will split the Java source files into three different directories:

For completeness and documentation purposes, we will apply the same principle to the test source directories. Note that in our current example this is not (currently) required; we will share the same tests for both targets. Sometimes this might not be possible and we might need to also split the test source files.

To do it, we will introduce the build-helper-maven-plugin to our Maven build. Its documentation says:

(The build-helper-maven-plugin) contains various small independent goals to assist with the Maven build lifecycle.

We are interested in two of these goals:

The full version of the modified pom.xml can be found here.

So we add the build-helper-maven-plugin to our profiles. Let's first set its version in the plugin management section:

<build>
    <pluginManagement>
        <plugins>
            ...
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <version>3.3.0</version>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

Next, we configure it in the Java 17 profile. We will add both the java17 and the java8 source sets:

<profiles>
    <profile>
        <id>java17</id>
        ...
        <build>
            <plugins>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>build-helper-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>add-source</id>
                            <phase>generate-sources</phase>
                            <goals>
                                <goal>add-source</goal>
                            </goals>
                            <configuration>
                                <sources>
                                    <source>src/main/java17</source>
                                    <source>src/main/java8</source>
                                </sources>
                            </configuration>
                        </execution>
                        <execution>
                            <id>add-test-source</id>
                            <phase>generate-test-sources</phase>
                            <goals>
                                <goal>add-test-source</goal>
                            </goals>
                            <configuration>
                                <sources>
                                    <source>src/test/java17</source>
                                    <source>src/test/java8</source>
                                </sources>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Lastly, we configure it for the Java 8 profile. We will add only the java8 source sets:

<profiles>
    <profile>
        <id>java8</id>
        ...
        <build>
            <plugins>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>build-helper-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>add-source</id>
                            <phase>generate-sources</phase>
                            <goals>
                                <goal>add-source</goal>
                            </goals>
                            <configuration>
                                <sources>
                                    <source>src/main/java8</source>
                                </sources>
                            </configuration>
                        </execution>
                        <execution>
                            <id>add-test-source</id>
                            <phase>generate-test-sources</phase>
                            <goals>
                                <goal>add-test-source</goal>
                            </goals>
                            <configuration>
                                <sources>
                                    <source>src/test/java8</source>
                                </sources>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Once again, we are only adding the test source directories for completeness; it is not (currently) required in our current example.

Let's get the update version of our files:

$ rm --recursive projects/
$ wget --cut-dirs=3 \
       --no-host-directories \
       --no-parent \
       --recursive \
       --reject "index.html*" \
       https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v3/index.html

And verify what we got:

$ find projects/ -type f
projects/library/src/main/java/library/ByteArrays.java
projects/library/src/main/java17/module-info.java
projects/library/src/test/java/library/ByteArraysTest.java
projects/library/pom.xml

We can see that the module-info.java has been moved to the Java 17 specific source folder. This makes sense as it is an invalid Java file for the Java 8 compiler. Let's check if the build-helper-maven-plugin is working as expected in the Java 17 build:

$ JAVA_HOME=jdk17/ mvn3/bin/mvn \
    --activate-profiles java17 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 3-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ library ---
[INFO]
[INFO] --- build-helper-maven-plugin:3.3.0:add-source (add-source) @ library ---
Downloading from rio: http://rio/nexus/content/groups/public/org/codehaus/plexus/plexus-component-annotations/1.5.5/plexus-component-annotations-1.5.5.pom
Downloaded from rio: http://rio/nexus/content/groups/public/org/codehaus/plexus/plexus-component-annotations/1.5.5/plexus-component-annotations-1.5.5.pom (815 B at 2.4 kB/s)
(...)
[INFO] Source directory: /home/mendo/o7/projects/library/src/main/java17 added.
[INFO] Source directory: /home/mendo/o7/projects/library/src/main/java8 added.
[INFO]
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  15.353 s
[INFO] Finished at: 2022-01-25T08:32:15-03:00
[INFO] ------------------------------------------------------------------------

OK, the Java 17 build passed and we confirmed the build-helper-maven-plugin did its work.

Let's check that the Java 8 still fails with a compilation error:

$ JAVA_HOME=jdk8/ mvn3/bin/mvn \
    --activate-profiles java8 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 3-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] --- build-helper-maven-plugin:3.3.0:add-source (add-source) @ library ---
[INFO] Source directory: /home/mendo/o7/projects/library/src/main/java8 added.
(...)
[INFO] --- maven-compiler-plugin:3.9.0:compile (default-compile) @ library ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/mendo/o7/projects/library/target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /home/mendo/o7/projects/library/src/main/java/library/ByteArrays.java:[24,18] cannot find symbol
  symbol:   method mismatch(byte[],byte[])
  location: class java.util.Arrays
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  4.357 s
[INFO] Finished at: 2022-01-25T08:35:37-03:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.9.0:compile (default-compile) on project library: Compilation failure
[ERROR] /home/mendo/o7/projects/library/src/main/java/library/ByteArrays.java:[24,18] cannot find symbol
[ERROR]   symbol:   method mismatch(byte[],byte[])
[ERROR]   location: class java.util.Arrays
[ERROR]
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

OK, it failed as expected.

In the next section we will try and fix this compilation error.

Refactoring our library: adding the Mismatch singleton

In a earlier section, we laid out the singleton solution we will use. In this section we will refactor our project to introduce the singleton to the Java 17 build.

The link in each class name points to the respective Java file.

In the src/main/java directory, we create the Mismatch abstract class:

package library;

abstract class Mismatch {
  abstract int mismatch(byte[] a, byte[] b);
}

In the src/main/java17 directory, we create the MismatchJava17 implementation:

package library;

import java.util.Arrays;

final class MismatchJava17 extends Mismatch {
  static final MismatchJava17 INSTANCE = new MismatchJava17();

  @Override
  final int mismatch(byte[] a, byte[] b) {
    return Arrays.mismatch(a, b);
  }
}

In the src/main/java directory, we create the MismatchSingleton which works as a symlink to MismatchJava17:

package library;

final class MismatchSingleton {
  static final Mismatch INSTANCE = MismatchJava17.INSTANCE;
}

Finally, in the src/main/java directory, we alter the ByteArrays class to refer to the symlink (MismatchSingleton in this analogy) and not to the target path (MismatchJava17 in this analogy):

package library;

public final class ByteArrays {
  private ByteArrays() {}

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

Let's get the new version of our project:

$ rm --recursive projects/
$ wget --cut-dirs=3 \
       --no-host-directories \
       --no-parent \
       --recursive \
       --reject "index.html*" \
       https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v4/index.html

And we verify that we got all of the files:

$ find projects/ -type f
projects/library/src/main/java/library/MismatchSingleton.java
projects/library/src/main/java/library/ByteArrays.java
projects/library/src/main/java/library/Mismatch.java
projects/library/src/main/java17/module-info.java
projects/library/src/main/java17/library/MismatchJava17.java
projects/library/src/test/java/library/ByteArraysTest.java
projects/library/pom.xml

Let's check if the Java 17 build passes:

$ JAVA_HOME=jdk17/ mvn3/bin/mvn \
    --activate-profiles java17 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 4-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  4.789 s
[INFO] Finished at: 2022-01-25T09:29:43-03:00
[INFO] ------------------------------------------------------------------------

OK, Java 17 build successful.

Finally, let's check that the Java 8 build now fails with a different compilation error. It should fail as the MismatchJava17 type referenced by the MismatchSingleton class does not exist in the Java 8 source set; it exists only in the Java 17 source set:

$ JAVA_HOME=jdk8/ mvn3/bin/mvn \
    --activate-profiles java8 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 4-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] --- maven-compiler-plugin:3.9.0:compile (default-compile) @ library ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 3 source files to /home/mendo/o7/projects/library/target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /home/mendo/o7/projects/library/src/main/java/library/MismatchSingleton.java:[19,43] cannot find symbol
  symbol:   variable MismatchJava17
  location: class library.MismatchSingleton
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.395 s
[INFO] Finished at: 2022-01-25T09:31:48-03:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.9.0:compile (default-compile) on project library: Compilation failure
[ERROR] /home/mendo/o7/projects/library/src/main/java/library/MismatchSingleton.java:[19,43] cannot find symbol
[ERROR]   symbol:   variable MismatchJava17
[ERROR]   location: class library.MismatchSingleton
[ERROR]
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

OK, it failed as expected.

In the next section we will add the Java 8 concrete class for the Mismatch abstract class.

MismatchJava8.java

We are now done with our refactorings. We can now do the actual Java 8 implementation of our library.

As mentioned in a earlier section, the Java 8 version will use the "compare the arrays one byte at time" solution. So let's create the java file for the MismatchJava8 class. Here is a possible implementation:

package library;

import java.util.Objects;

final class MismatchJava8 extends Mismatch {
  static final MismatchJava8 INSTANCE = new MismatchJava8();

  @Override
  final int mismatch(byte[] a, byte[] b) {
    Objects.requireNonNull(a, "a == null");
    Objects.requireNonNull(b, "b == null");

    int length;
    length = Math.min(a.length, b.length);

    for (int i = 0; i < length; i++) {
      if (a[i] != b[i]) {
        return i;
      }
    }

    if (a.length == b.length) {
      return -1;
    } else {
      return length;
    }
  }
}

Before we test the Maven build, we must edit the MismatchSingleton java file for it to use the newly created type:

package library;

final class MismatchSingleton {
  static final Mismatch INSTANCE = MismatchJava8.INSTANCE;
}

Let's get the new version of our project:

$ rm --recursive projects/
$ wget --cut-dirs=3 \
       --no-host-directories \
       --no-parent \
       --recursive \
       --reject "index.html*" \
       https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v5/index.html

And we verify that we got all of the files:

$ find projects/ -type f | sort
projects/library/pom.xml
projects/library/src/main/java/library/ByteArrays.java
projects/library/src/main/java/library/Mismatch.java
projects/library/src/main/java/library/MismatchSingleton.java
projects/library/src/main/java17/library/MismatchJava17.java
projects/library/src/main/java17/module-info.java
projects/library/src/main/java8/library/MismatchJava8.java
projects/library/src/test/java/library/ByteArraysTest.java

Let's test our Java 8 version now:

$ JAVA_HOME=jdk8/ mvn3/bin/mvn \
    --activate-profiles java8 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 5-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] --- build-helper-maven-plugin:3.3.0:add-source (add-source) @ library ---
[INFO] Source directory: /home/mendo/o7/projects/library/src/main/java8 added.
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.802 s
[INFO] Finished at: 2022-01-25T10:29:02-03:00
[INFO] ------------------------------------------------------------------------

OK, the build was successful.

Improving our build process

So far we managed to build, from a single project, two different JAR artifacts:

The process, however, is still lacking:

In the following sections we will incrementally improve our build process aiming to resolve these issues.

Adding objectos-latest

In the section where we implemented the MismatchJava8 class we had to manually edit the MismatchSingleton.java file in order to switch from the Java 17 implementation to the Java 8 one. This is not practical in a day-to-day operation. Let's automate this process using Objectos Latest.

Objectos Latest, which might be a slight misnomer, is an annotation processor that generates a Java file based on the subclasses of an annotated type. It selects, among the subclasses declared in the same package of the annotated type, the one that represents the latest version. More precisely, it expects the subclasses' names to end with an integer number. Then, among all of the available subclasses, it selects the one having the greatest of such numbers.

To get started with Objectos Latest, let's declare it in our project's POM file. Here is the full version of the updated POM file if you wish to follow along.

We will declare it in the POM 'common' section as both profiles will require it. We will declare it in two different parts:

  1. as an annotationProcessorPaths option to the maven-compiler-plugin. This is to instruct the Maven compiler plugin to only search for annotation processors in these elements; and

  2. as an optional dependency of the whole project. This is to make the Objectos Latest annotations available to the project.

<project>
    ...
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>${objectos.groupId}</groupId>
                            <artifactId>objectos-latest</artifactId>
                            <version>${objectos.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>${objectos.groupId}</groupId>
            <artifactId>objectos-latest</artifactId>
            <version>${objectos.version}</version>
            <optional>true</optional>
        </dependency>
        ...
    </dependencies>
</project>

The Java 17 implementation of our library is modular. In order to annotate our classes with annotations from the Objectos Latest library, we need to declare that our library's module depend on the Objectos Latest module. We do so by declaring this dependency in the module-info.java file, like so:

module library {
  exports library;

  requires static br.com.objectos.latest;
}

We declared the dependency as static as the annotations (and their processing for that matter) are required at compile time only.

Now we have to annotate our classes. We annotate the superclass Mismatch with the @Singleton annotation, like so:

package library;

import br.com.objectos.latest.Singleton;

@Singleton
abstract class Mismatch {
  abstract int mismatch(byte[] a, byte[] b);
}

Next, we annotate the field of the MismatchJava17 class that is referencing the singleton instance with the @Singleton.Field annotation:

package library;

import br.com.objectos.latest.Singleton;
import java.util.Arrays;

final class MismatchJava17 extends Mismatch {
  @Singleton.Field
  static final MismatchJava17 INSTANCE = new MismatchJava17();

  //...
}

And we do the same with the MismatchJava8 class:

package library;

import br.com.objectos.latest.Singleton;
import java.util.Objects;

final class MismatchJava8 extends Mismatch {
  @Singleton.Field
  static final MismatchJava8 INSTANCE = new MismatchJava8();

  //...
}

Finally, as Objectos Latest will generate the Java file for the MismatchSingleton class, we need to remove the current existing file from our project.

Let's get the new version of our project:

$ rm --recursive projects/
$ wget --cut-dirs=3 \
       --no-host-directories \
       --no-parent \
       --recursive \
       --reject "index.html*" \
       https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v6/index.html

And we verify that we got all of the files:

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

Note that the MismatchSingleton.java file is not there anymore.

Let's check the Java 17 build:

$ JAVA_HOME=jdk17/ mvn3/bin/mvn \
    --activate-profiles java17 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 6-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from rio: http://rio/nexus/content/groups/public/br/com/objectos/oss-java17/objectos-latest/3.1.0/objectos-latest-3.1.0.pom
Downloaded from rio: http://rio/nexus/content/groups/public/br/com/objectos/oss-java17/objectos-latest/3.1.0/objectos-latest-3.1.0.pom (1.8 kB at 1.7 kB/s)
(...)
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  4.125 s
[INFO] Finished at: 2022-01-25T15:01:32-03:00
[INFO] ------------------------------------------------------------------------

Nice, the build was successful. Let's check the generated file:

$ cat projects/library/target/generated-sources/annotations/library/MismatchSingleton.java
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() {}
}

And let's check the Java 8 build:

$ JAVA_HOME=jdk8/ mvn3/bin/mvn \
    --activate-profiles java8 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 6-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from rio: http://rio/nexus/content/groups/public/br/com/objectos/oss-java8/objectos-latest/3.1.0/objectos-latest-3.1.0.pom
Downloaded from rio: http://rio/nexus/content/groups/public/br/com/objectos/oss-java8/objectos-latest/3.1.0/objectos-latest-3.1.0.pom (0 B at 0 B/s)
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.678 s
[INFO] Finished at: 2022-01-25T15:04:29-03:00
[INFO] ------------------------------------------------------------------------

The Java 8 build was also successful. Let's check the generated file:

$ cat projects/library/target/generated-sources/annotations/library/MismatchSingleton.java
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() {}
}

OK, the annotation processor from the Objectos Latest library is working properly.

Adding maven-toolchains-plugin

In this section, we will introduce the maven-toolchains-plugin to our build process.

To understand why this plugin might be required, suppose we were targeting our library to work in a Java 7 environment. This means we would need to build our library with JDK 7. According to the Maven download page, the minimum JDK version it can execute on is JDK 7. So we could just get ourselves a JDK 7 and, as we have been doing, build our project with something like:

JAVA_HOME=jdk7/ mvn3/bin/mvn ...

It should work without major issues (I think). Now suppose you require, in your build process, say a plugin that manages the license headers in your source files. It seems this particular plugin requires at least JDK 8 to execute. If we were to introduce this plugin to our build, our build would fail; Maven would be running on a Java 7 JVM and would not be able to load and/or parse the bytecode of the plugin.

The solution in this case is to run Maven with the latest JDK and let Maven select, at runtime, which JDK it should use to compile Java code, run tests or to generate Javadocs. Maven provides this functionality through the maven-toolchains-plugin.

It should follow from this introduction that adding this plugin to our current example is not strictly required; we are not targeting Java 7 nor are we using a plugin that is incompatible with Java 8. We will do it anyways for completeness and documentation purposes; at time of writing, the actual Objectos Libraries target Java 6, 7, 8, 11 and 17.

To get started with the maven-toolchains-plugin let's create a toolchains.xml file. This file is required and defines and configures the available JDKs that can be used by the plugin. You can find the full version of the file here.

Maven by default looks for this file at ~/.m2/toolchains.xml. We will, however, create it at our working directory as to not mess up the standard file. There are two required sections that need configuring:

So let's add the two JDKs we are using:

<toolchains>
    <toolchain>
        <type>jdk</type>
        <provides>
            <version>1.8</version>
        </provides>
        <configuration>
            <jdkHome>jdk8/</jdkHome>
        </configuration>
    </toolchain>
    <toolchain>
        <type>jdk</type>
        <provides>
            <version>17</version>
        </provides>
        <configuration>
            <jdkHome>jdk17/</jdkHome>
        </configuration>
    </toolchain>
</toolchains>

In a proper toolchains file it would be advisable to use absolute path names.

Next, we must declare the plugin it in our project's POM file. In the following link you will find the full version of the updated POM file.

First, let's declare the plugin in our pluginManagement and plugins sections:

<project>
    ...
    <build>
        <pluginManagement>
            <plugins>
                ...
                <plugin>
                    <artifactId>maven-toolchains-plugin</artifactId>
                    <version>3.0.0</version>
                </plugin>
                ...
            </plugins>
        </pluginManagement>
        <plugins>
            ...
            <plugin>
                <artifactId>maven-toolchains-plugin</artifactId>
                <executions>
                    <execution>
                        <id>toolchains-jdk</id>
                        <goals>
                            <goal>toolchain</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <toolchains>
                        <jdk>
                            <version>${maven.toolchains.jdk-version}</version>
                        </jdk>
                    </toolchains>
                </configuration>
            </plugin>
        </plugins>
    </build>
    ...
</project>

In the plugin's configuration section, we instruct it to look for, in our toolchains.xml file, a JDK providing a version tag with a specific value. The value itself is parameterized. Let's set its actual value in each of our Maven profiles:

<profiles>
    <profile>
        <id>java17</id>
        <properties>
            ...
            <maven.toolchains.jdk-version>17</maven.toolchains.jdk-version>
            ...
        </properties>
        ...
    </profile>
    <profile>
        <id>java8</id>
        <properties>
            ...
            <maven.toolchains.jdk-version>1.8</maven.toolchains.jdk-version>
            ...
        </properties>
        ...
    </profile>
</profiles>

Let's get the new version of our project:

$ rm --recursive projects/
$ wget --cut-dirs=3 \
       --no-host-directories \
       --no-parent \
       --recursive \
       --reject "index.html*" \
       https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v7/index.html

And we verify if we got all of the files:

$ find m2/ projects/ -type f ! -path "m2/repository*" | sort
m2/toolchains.xml
projects/library/pom.xml
projects/library/src/main/java/library/ByteArrays.java
projects/library/src/main/java/library/Mismatch.java
projects/library/src/main/java17/library/MismatchJava17.java
projects/library/src/main/java17/module-info.java
projects/library/src/main/java8/library/MismatchJava8.java
projects/library/src/test/java/library/ByteArraysTest.java

And now let's run Maven using our system's JDK. On my system, this is the version I have:

$ 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)

In theory, for this example, any JDK 7+ should work. And now we run Maven, first the Java 17 profile:

$ mvn3/bin/mvn \
    --activate-profiles java17 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    --toolchains m2/toolchains.xml \
    clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 7-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from rio: http://rio/nexus/content/groups/public/org/apache/maven/plugins/maven-toolchains-plugin/3.0.0/maven-toolchains-plugin-3.0.0.pom
Downloaded from rio: http://rio/nexus/content/groups/public/org/apache/maven/plugins/maven-toolchains-plugin/3.0.0/maven-toolchains-plugin-3.0.0.pom (3.6 kB at 3.4 kB/s)
(...)
[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/]
[INFO]
(...)
[INFO] --- maven-compiler-plugin:3.9.0:compile (default-compile) @ library ---
[INFO] Toolchain in maven-compiler-plugin: JDK[jdk17/]
(...)
[INFO]
[INFO] --- maven-surefire-plugin:3.0.0-M5:test (default-test) @ library ---
[INFO] Toolchain in maven-surefire-plugin: JDK[jdk17/]
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  11.101 s
[INFO] Finished at: 2022-01-26T11:32:01-03:00
[INFO] ------------------------------------------------------------------------

We can see from the output that both the maven-compiler-plugin and the maven-surefire-plugin picked up the correct JDK. Let's confirm it by running now the Java 8 profile:

$ mvn3/bin/mvn \
    --activate-profiles java8 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    --toolchains m2/toolchains.xml \
    clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 7-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[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/]
[INFO]
(...)
[INFO] --- maven-compiler-plugin:3.9.0:compile (default-compile) @ library ---
[INFO] Toolchain in maven-compiler-plugin: JDK[jdk8/]
(...)
[INFO] --- maven-surefire-plugin:3.0.0-M5:test (default-test) @ library ---
[INFO] Toolchain in maven-surefire-plugin: JDK[jdk8/]
(...)
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  6.970 s
[INFO] Finished at: 2022-01-26T11:39:02-03:00
[INFO] ------------------------------------------------------------------------

Once again, both the maven-compiler-plugin and the maven-surefire-plugin picked up the correct JDK.

So Maven itself is running on our system's JDK. For the Java 17 build, it selects the JDK located at the jdk17/ directory. For the Java 8 build, it selects the JDK located at the jdk8/ directory.

Splitting the build directory

To build our project we are always running the Maven clean phase. In our current setup, although not strictly required in theory, it is the safest option. To illustrate this fact, let's see what happens if build the two profiles without running clean between runs.

Suppose we are debugging some issue and we have to switch between builds. Let's run the Java 8 profile. For this first run, we invoke the clean and test phases:

$ mvn3/bin/mvn \
    --activate-profiles java8 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    --toolchains m2/toolchains.xml \
    clean test
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 7-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.902 s
[INFO] Finished at: 2022-01-26T18:16:41-03:00
[INFO] ------------------------------------------------------------------------

OK, the build was successful as expected.

Now, let's run the Java 17 profile, just the test phase:

$ mvn3/bin/mvn \
    --activate-profiles java17 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    --toolchains m2/toolchains.xml \
    test
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 7-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  4.716 s
[INFO] Finished at: 2022-01-26T18:17:13-03:00
[INFO] ------------------------------------------------------------------------

OK, Maven recompiled everything and the build was successful.

Finally, let's switch back to the Java 8 profile, just the test phase:

$ mvn3/bin/mvn \
    --activate-profiles java8 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    --toolchains m2/toolchains.xml \
    test
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 7-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] --- maven-surefire-plugin:3.0.0-M5:test (default-test) @ library ---
[INFO] Toolchain in maven-surefire-plugin: JDK[jdk8/]
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[ERROR] library/ByteArraysTest has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 0, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.945 s
[INFO] Finished at: 2022-01-26T18:18:55-03:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.0.0-M5:test (default-test) on project library: There are test failures.
[ERROR]
[ERROR] Please refer to /home/mendo/o7/projects/library/target/surefire-reports for the individual test results.
[ERROR] Please refer to dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] There was an error in the forked process
[ERROR] library/ByteArraysTest has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0
[ERROR] org.apache.maven.surefire.booter.SurefireBooterForkException: There was an error in the forked process
[ERROR] library/ByteArraysTest has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0
[ERROR]         at org.apache.maven.plugin.surefire.booterclient.ForkStarter.fork(ForkStarter.java:733)
[ERROR]         at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:305)
[ERROR]         at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:265)
[ERROR]         at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeProvider(AbstractSurefireMojo.java:1314)
[ERROR]         at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeAfterPreconditionsChecked(AbstractSurefireMojo.java:1159)
[ERROR]         at org.apache.maven.plugin.surefire.AbstractSurefireMojo.execute(AbstractSurefireMojo.java:932)
(...)
[ERROR]
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException

The build failed. The classes were not recompiled; a Java 8 JVM tried to run test classes that were compiled with JDK 17.

One possible way to solve this issue is to split the Maven build directory. More precisely, one possible solution is to instruct Maven to use different build directories for each of our profiles. By default, Maven creates and uses a directory named target as the build directory. Maven creates this directory as a child of the directory where the pom.xml file is. To use another build directory, we have to update the build section of each of our profiles in our POM file.

Here is the full version of the updated POM file. The modified sections are shown below:

<profiles>
    <profile>
        <id>java17</id>
        ...
        <build>
            <directory>${project.basedir}/target-java17</directory>
            ...
        </build>
    </profile>
    <profile>
        <id>java8</id>
        ...
        <build>
            <directory>${project.basedir}/target-java8</directory>
            ...
        </build>
    </profile>
</profiles>

All right, let's get all of the files:

$ rm --recursive projects/
$ wget --cut-dirs=3 \
       --no-host-directories \
       --no-parent \
       --recursive \
       --reject "index.html*" \
       https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v8/index.html

Here is a listing of all of the files:

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

OK, now let's repeat the sequence of builds that failed before. We start building our Java 8 profile:

$ mvn3/bin/mvn \
    --activate-profiles java8 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    --toolchains m2/toolchains.xml \
    clean test
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 8-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[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/o7/projects/library/target-java8/classes
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  6.220 s
[INFO] Finished at: 2022-01-27T07:07:29-03:00
[INFO] ------------------------------------------------------------------------

From the output of the maven-compiler-plugin we can see that it picked up our new build directory location.

Next we build our Java 17 profile:

$ mvn3/bin/mvn \
    --activate-profiles java17 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    --toolchains m2/toolchains.xml \
    test
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 8-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] --- maven-compiler-plugin:3.9.0:compile (default-compile) @ library ---
[INFO] Toolchain in maven-compiler-plugin: JDK[jdk17/]
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 5 source files to /home/mendo/o7/projects/library/target-java17/classes
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  5.049 s
[INFO] Finished at: 2022-01-27T07:10:29-03:00
[INFO] ------------------------------------------------------------------------

And we build the Java 8 profile again, this time without the clean phase:

$ mvn3/bin/mvn \
    --activate-profiles java8 \
    --define maven.repo.local=m2/repository \
    --file projects/library/pom.xml \
    --toolchains m2/toolchains.xml \
    test
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 8-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.551 s
[INFO] Finished at: 2022-01-27T07:11:59-03:00
[INFO] ------------------------------------------------------------------------

OK, build was successful.

Splitting the local repository

In this section we will update our build process so that each profile uses a different local repository.

Currently, any produced JAR file is being installed in our local repository located at the m2/repository directory. If we do a mvn install with the Java 8 profile immediately followed by a mvn install with the Java 17 profile our local repository will contain only the Java 17 binaries.

To partially^^9^^ solve this, in a manner analogous to the previous section, we will instruct Maven to use different local repository locations.

Let's first remove our current local repository:

$ rm --recursive m2/repository/

Let's install the Java 17 profile to the m2/repo-java17 directory. Maven will create the directory so there is no need to create it ourselves:

$ mvn3/bin/mvn \
    --activate-profiles java17 \
    --define maven.repo.local=m2/repo-java17 \
    --file projects/library/pom.xml \
    --toolchains m2/toolchains.xml \
    install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 8-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
Downloaded from rio: http://rio/nexus/content/groups/public/org/apache/maven/shared/maven-shared-utils/3.1.0/maven-shared-utils-3.1.0.jar (164 kB at 835 kB/s)
Downloaded from rio: http://rio/nexus/content/groups/public/commons-io/commons-io/2.5/commons-io-2.5.jar (209 kB at 1.0 MB/s)
[INFO] Installing /home/mendo/o7/projects/library/target-java17/library-8-SNAPSHOT.jar to /home/mendo/o7/m2/repo-java17/br/com/objectos/www/library/8-SNAPSHOT/library-8-SNAPSHOT.jar
[INFO] Installing /home/mendo/o7/projects/library/pom.xml to /home/mendo/o7/m2/repo-java17/br/com/objectos/www/library/8-SNAPSHOT/library-8-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  51.893 s
[INFO] Finished at: 2022-01-27T08:39:03-03:00
[INFO] ------------------------------------------------------------------------

This first build takes longer as Maven has to re-download all of the plugins and dependencies. We can see that the artifact was installed at the m2/repo-java17 directory.

Let's now install the Java 8 profile to the m2/repo-java8 directory:

$ mvn3/bin/mvn \
    --activate-profiles java8 \
    --define maven.repo.local=m2/repo-java8 \
    --file projects/library/pom.xml \
    --toolchains m2/toolchains.xml \
    install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 8-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
Downloaded from rio: http://rio/nexus/content/groups/public/org/apache/maven/shared/maven-artifact-transfer/0.10.0/maven-artifact-transfer-0.10.0.jar (128 kB at 680 kB/s)
Downloaded from rio: http://rio/nexus/content/groups/public/org/apache/maven/shared/maven-shared-utils/3.1.0/maven-shared-utils-3.1.0.jar (164 kB at 871 kB/s)
[INFO] Installing /home/mendo/o7/projects/library/target-java8/library-8-SNAPSHOT.jar to /home/mendo/o7/m2/repo-java8/br/com/objectos/www/library/8-SNAPSHOT/library-8-SNAPSHOT.jar
[INFO] Installing /home/mendo/o7/projects/library/pom.xml to /home/mendo/o7/m2/repo-java8/br/com/objectos/www/library/8-SNAPSHOT/library-8-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  50.424 s
[INFO] Finished at: 2022-01-27T08:42:20-03:00
[INFO] ------------------------------------------------------------------------

And we can see that the artifact was installed at the m2/repo-java8 directory.

Creating convenience shell scripts

So far, each time that we invoke Maven, we have been setting all of the options using their long form. This is intentional. However, in a day-to-day operation, it might become error-prone.

Let's create a simple shell script for each of our profiles. They will reside in a bin directory and both have the following general form:

#!/bin/bash

P="javaX"

OPTS=(
    --activate-profiles ${P}
    --define maven.repo.local=m2/repo-${P}
    --file projects/library/pom.xml
    --toolchains m2/toolchains.xml
)

mvn3/bin/mvn "${OPTS[@]}" "$@"

And here are the full versions of the scripts:

We get all of the files:

$ rm --recursive projects/
$ wget --cut-dirs=3 \
     --no-host-directories \
     --no-parent \
     --recursive \
     --reject "index.html*" \
     https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v9/index.html

We should have the following:

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

Just to be safe, confirm the contents of the scripts:

$ cat bin/mvn*

Make the scripts executable:

$ chmod +x bin/mvn*

And build. First the Java 17 profile:

$ bin/mvn17 verify
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 9-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[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/]
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.306 s
[INFO] Finished at: 2022-01-28T07:52:17-03:00
[INFO] ------------------------------------------------------------------------

Then the Java 8 profile:

$ bin/mvn8 verify
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 9-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[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/]
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  4.285 s
[INFO] Finished at: 2022-01-28T07:53:20-03:00
[INFO] ------------------------------------------------------------------------

Next on this series

In this first post of the series, we produced two different binaries of our library, one binary targeting Java 8 and the other targeting Java 17. Each binary is built and tested using their specific JDK. Both binaries are produced from a single Maven project.

In the next posts of this series:

Footnotes

[1]: I will write about the Objectos versioning in a future post. For now, I would like you to know that, inspired by the JDK, I adopt a time-based release model. The difference is that I release a new version of the Objectos libraries every quarter while the JDK is released every six months. Therefore, version 3.1.0 is just the fourth version released publicly. By this I mean that, although it is a 'major' version and although it is being used in production, it is still a young project. For all practical purposes, its API should still be considered beta. Please be warned. back to article{#fn1}

[2]: You should also use the oss-java11 group ID if you are running Java runtimes versions 12 up to 16 (inclusive). Note, however, that the libraries are not tested on those environments and are, therefore, unsupported. To be precise, at time of writing, no runtime is supported at all. By this I mean that software is offered "AS IS" as per Apache 2.0 License. Proper commercial support and LTS versions are on the roadmap. Please e-mail me if you are interested in commercial support now. back to article{#fn2}

[3]: They all provide the same core API. A higher group ID might offer additional API on top of the lower group ID. In other words, you can easily move from a lower group ID to a higher group ID (provided you also upgrade your Java runtime). The other way around, not so much. back to article{#fn3}

[4]: I should note that the artifacts from lower group IDs (oss-java6, oss-java7 and oss-java8) do not declare the Automatic-Module-Name JAR manifest attribute. This is intentional. It is intended to discourage the use of these group IDs in the module-path. If your application is modular then you MUST use group ID oss-java11 or greater. back to article{#fn4}

[5]: To be precise, we will not actually release the library to Maven central, I will just give you the directions. back to article{#fn5}

[6]: To be precise, at time of writing, Objectos Git will not benefit from this. Git loose object prefix like 'commit ' have less than 8 bytes in length. However, two libraries on the roadmap namely Objectos HTTP and Objectos SMTP will probably benefit from this. back to article{#fn6}

[7]: I could create an implementation similar to the ArraySupport.vectorizedMismatch using Unsafe. A sort of indirect backport. Maybe in a future post and/or a future release of the Objectos libraries. back to article{#fn7}

[8]: I am aware of the benefits of mvn verify. However, I often use the --resume-from option and MNG-4660 is not released yet as it currently targets Maven 4. So I use mvn install more often. back to article{#fn8}

[9]: This is a partial solution because both the Java 8 artifact and the Java 17 artifact still have the same Maven coordinates. That is, both artifacts share the same group ID, the same artifact ID and the same version. We will address this issue in a upcoming post. back to article{#fn9}