Are you seriously not using Java 15 yet?

Posted on



Introduction

  • What I want to accomplish: I want to move to Amazon Corretto 15 so I can use the new features in my work projects. These features feel long awaited; So much so, Kotlin and Lombok continue to gain popularity.
  • How: I plan to convert an existing Java project from amazon‑corretto‑11 to amazon‑corretto‑15, and add code to the project which will leverage the new features just to see how our infrastructure handles them.
  • Caveats: This particular service is dockerized. So we would also need to update our docker image to use Java 15. I plan to run the project outside of docker just using mvn from a local shell.



Conversion process



Change the Maven compiler plugin version.

In the build section of the pom.xml file:

    <build>
        <plugins>
            . . .
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <release>15</release>
                    <compilerArgs>
                        <arg>--enable-preview</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
            . . .
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <forkCount>0</forkCount>
                    <argLine>--enable-preview</argLine>
                </configuration>
            </plugin>
            . . .
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <configuration>
                    <argLine>--enable-preview</argLine>
                </configuration>
            </plugin>
        </plugins>
    <build>
Enter fullscreen mode

Exit fullscreen mode

I needed forkCount in the surefire plugin configuration because the parent pom declares something higher than 0, SUREFIRE-1528.

This is a great reminder that some of the features in 15 are technically experimental still and require preview.
While I would be hesitant to use preview features in a production system, Amazon has faithfully iterated on corretto-11 through patches. Thus, as issues arise, there is iteration to fix those problems, and I believe that would also extend to preview features.
And I think it’s safe to assume that corretto-15 will also have patches issued.



Installing Amazon Corretto 15

Download amazon-corretto-15

Or for the Linux users, you couldn’t ask for a sweeter time: Linux package managed installation instructions



Updating my IDE



IntelliJ

Download the latest IntelliJ and you will have been all set to use Java 15 since version 2020.2!



Eclipse

Download the latest Eclipse



Using the JDK

Just make sure when you’re setting up your IDE, that you point to the correctto JDK installation!



Let’s try out these Java 15 features



No more need for lombok here | Records

Here is a simple class in the existing project. Users in this domain are given a “baby step” between 0 and 7.

With Lombok:

@lombok.Data
public class UserBabyStep {
  public static final UserBabyStep NO_BABY_STEP = new UserBabyStep();

  public static final int DEFAULT_BABY_STEP = 0;
  public static final int MAX_BABY_STEP = 7;

  private final int value;

  public UserBabyStep() {
    this.value = DEFAULT_BABY_STEP;
  }

  public UserBabyStep(final int value) {
    if (value < 0 || value > MAX_BABY_STEP)
      throw new IllegalArgumentException(
          "The Baby Step of a user must be between 0 and 7 inclusively.");
    this.value = value;
  }
}
Enter fullscreen mode

Exit fullscreen mode

Hello records!

public record UserBabyStep(int value) {
  public static final UserBabyStep NO_BABY_STEP = new UserBabyStep();

  public static final int DEFAULT_BABY_STEP = 0;
  public static final int MAX_BABY_STEP = 7;

  public UserBabyStep() {
    this(DEFAULT_BABY_STEP);
  }

  public UserBabyStep(final int value) {
    if (value < 0 || value > MAX_BABY_STEP)
      throw new IllegalArgumentException(
          "The Baby Step of a user must be between 0 and 7 inclusively.");
    this.value = value;
  }
}
Enter fullscreen mode

Exit fullscreen mode



Notes for record

Lombok will generate a more Java‑canonical getter method for fields: getValue(). The getter method produced by the record feature is: value().
This is more similar to how the Immutables library operates, and is a fancy way to get around when you may, or may not want isFlag for boolean getters. Especially if you are like me, and prefer leveraging the type system for wrapping primitives in a more domain‑driven class.

Another note is that record‑declared classes cannot extend another class! Only implement interfaces.



Necessary usage of the canonical constructor

Finally, when using records, all constructors must call the canonical constructor. That is, the constructor with the same types, order, and number of arguments that are declared on the record itself.



Who’s up for Checkers?

Instead of just looking for more things to change around in this existing project, I’m going to add irrelevant code simply for the sake of flexing the new features. And then, I want to see how it compiles and how the project plugins handle it.

Also, I isolated the checkers code if you’d like to take a closer look.



Sealed Interfaces

I’m going to mess around with this here, however, see the note at the end of the article concerning sealed interfaces.

A checker board tends to have two alternating colors tiling a 8 x 8 board.

checkerboard

Each space/tile can have contents; a red or black token.

To demonstrate the difference between individual tiles, there is a sealed interface called Space which permits a BlackSpace and a RedSpace. Each subclass then also implements the wither method that allows a space to take new contents.

public enum Contents {
  EMPTY, BLACK, RED;
}

public static sealed interface Space permits BlackSpace, RedSpace {
  public Contents contents();
  public int x();
  public int y();
  public Space withContents(final Contents state);

  public default boolean isEmpty() {
    return contents() == Contents.EMPTY;
  }

  public default boolean contains(final Contents state) {
    return contents() == state;
  }

  public record BlackSpace(Contents contents, int x, int y) implements Space {
    public BlackSpace(final int x, final int y) {
      this(Contents.EMPTY, x, y);
    }

    @Override
    public Space withContents(final Contents contents) {
      return new BlackSpace(contents, x, y);
    }
  }

  public record RedSpace(Contents contents, int x, int y) implements Space {
    public RedSpace(final int x, final int y) {
      this(Contents.EMPTY, x, y);
    }

    @Override
    public Space withContents(final Contents contents) {
      return new RedSpace(contents, x, y);
    }
  }
}
Enter fullscreen mode

Exit fullscreen mode

Now our CheckerBoard can look a little something like this:

@SuppressWarnings("preview")
public class CheckerBoard {
  private static int MAX = 8;
  private final Space[][] spaces;

  private CheckerBoard() {
    spaces = new Space[MAX][MAX];
    forEachSpace(
        (i, j) -> spaces[i][j] =
          ((i + j) % 2 == 0)
            ? new BlackSpace(i, j)
            : new RedSpace(i, j));
  }

  . . .

}
Enter fullscreen mode

Exit fullscreen mode

Note: that, if you are anything like me, you aren’t a fan of warnings. So here’s a way to suppress them for the preview features we’ll be using.



Multi-line Strings

Java eliminates all leading whitespace between newlines!
Luckily, I already had a scenario where I would not need leading spaces in my string anyways:

class CheckerBoardTest {
  @Test
  void shouldPrintBoardAsExpected() {
    final CheckerBoard subject = CheckerBoard.freshBoardWithTokens();

    assertThat(subject.toString())
      .isEqualTo("""
            Ω · Ω · Ω · Ω ·
            · Ω · Ω · Ω · Ω
            Ω · Ω · Ω · Ω ·
            · • · • · • · •
            • · • · • · • ·
            · ☺ · ☺ · ☺ · ☺
            ☺ · ☺ · ☺ · ☺ ·
            · ☺ · ☺ · ☺ · ☺
            """);
  }
}
Enter fullscreen mode

Exit fullscreen mode

The google format spec basically has no idea what to do with this…



Pattern Matching in instanceof (instanceof casting)

This is a really handy trick that I wish was introduced much earlier.

It is also reminiscent of a feature of the AssertJ testing library.

The key use of this feature is to prevent a manual class cast of a variable after you are only in a block of code based on the type of a variable.

So instead of:

public void method(final Animal animal) {
  if (animal instanceof Cat) {
    ((Cat) animal).meow();
  }
}
Enter fullscreen mode

Exit fullscreen mode

We can do:

public void method(final Animal animal) {
  if (animal instanceof Cat cat) {
    cat.meow();
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *