Are you seriously not using Java 15 yet?
Tags: java, java15, amazoncorretto, maven
Published: 2021-02-10 18:08:20

---
title: Are you seriously not using Java 15 yet?
published: true
description: Experimenting with the latest and greatest features of Java 15 is a lot of fun! But how feasible is its use in your existing Java project?
tags: java, java15, amazoncorretto, maven
cover_image: https://pbs.twimg.com/media/EiC-NwHXkAUufg1.jpg:large
---

# Introduction
* **What I want to accomplish**: I want to move to [Amazon Corretto 15](https://docs.aws.amazon.com/corretto/latest/corretto-15-ug/what-is-corretto-15.html) so I can use the new features in my work projects. These features feel long awaited; So much so, [Kotlin](https://snyk.io/blog/kotlin-overtakes-scala-and-clojure-to-become-the-2nd-most-popular-language-on-the-jvm/#:~:text=Particularly%20Kotlin%2C%20a%20language%20developed,%2C%20to%20an%20impressive%205.5%25.) and [Lombok](https://www.toptal.com/java/write-fat-free-java-code-project-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:

```xml
    <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>
```

*[I needed `forkCount` in the `surefire` plugin configuration](https://stackoverflow.com/questions/55303837/problem-running-tests-with-enabled-preview-features-in-surefire-and-failsafe/55311987#55311987) because the parent pom declares something higher than `0`, [SUREFIRE-1528](https://issues.apache.org/jira/projects/SUREFIRE/issues/SUREFIRE-1528?filter=allopenissues).*

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](https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/patches.html) 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](https://docs.aws.amazon.com/corretto/latest/corretto-15-ug/patches.html).

## Installing Amazon Corretto 15

[Download `amazon-corretto-15`](https://docs.aws.amazon.com/corretto/latest/corretto-15-ug/downloads-list.html)

Or for the Linux users, you couldn't ask for a sweeter time: [Linux package managed installation instructions](https://docs.aws.amazon.com/corretto/latest/corretto-15-ug/generic-linux-install.html)

## Updating my IDE

### IntelliJ

[Download the latest IntelliJ](https://www.jetbrains.com/idea/download) and you will have been all set to use [Java 15 since version 2020.2](https://blog.jetbrains.com/idea/2020/09/java-15-and-intellij-idea/)!

### Eclipse

[Download the latest Eclipse](https://www.eclipse.org/downloads/)

### Using the JDK

Just make sure when you're setting up your IDE, that you point to the correct<span style="color:gray"><em>to</em></span> 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:

```java
@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;
  }
}
```

Hello `records`!

```java
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;
  }
}
```

### 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](https://immutables.github.io/) 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](https://github.com/aaiezza/checkers) 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](https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT0Dlvfi2C_DcaNEtIYRaEEAGD-YVgJHRarbNM9m96QZBVb9nvcUBXm7RBu-mReO0T-Ba6fhFU&usqp=CAc)

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.

```java
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);
    }
  }
}
```

Now our `CheckerBoard` can look a little something like this:

```java
@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));
  }

  . . .

}
```

**Note**: that, if you are anything like me, you aren't a fan of warnings. So here's a way to [suppress them](https://stackoverflow.com/questions/59019993/how-can-i-suppress-javac-warning-about-preview-features) 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:

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

    assertThat(subject.toString())
      .isEqualTo("""
            Ω · Ω · Ω · Ω ·
            · Ω · Ω · Ω · Ω
            Ω · Ω · Ω · Ω ·
            · • · • · • · •
            • · • · • · • ·
            · ☺ · ☺ · ☺ · ☺
            ☺ · ☺ · ☺ · ☺ ·
            · ☺ · ☺ · ☺ · ☺
            """);
  }
}
```

*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](https://assertj.github.io/doc/#assertj-core-3.13.0-asInstanceOf).

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:

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

We can do:

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

This method will set up a new checkboard and place the players' tokens in their starting positions. Since pieces should only be allowed to be played to the black spaces on the board, I can place pieces using the a `wither` to keep each `space` instance of my board immutable. 

```java
public static CheckerBoard freshBoardWithTokens() {
  final CheckerBoard board = new CheckerBoard();

  board.forTopThreeRowsOfSpaces(space -> {
    if (space instanceof BlackSpace s) {
      board.spaces[s.x()][s.y()] = s.withContents(BLACK);
    }
  });

  board.forBottomThreeRowsOfSpaces(space -> {
    if (space instanceof BlackSpace s) {
      board.spaces[s.x()][s.y()] = s.withContents(RED);
    }
  });

  return board;
}
```

> To be fair, the cast here is not necessary. So if you can come up with a better use case, I'd be happy to hear about it in the comments!

### Switch Expression

Our `CheckerBoard` class has a convenient `toString` method for a user friendly visual:

```java
@Override
public String toString() {
  return streamSpaces()
    .map(space -> {
      final String strSpace = (switch (space.contents()) {
        case EMPTY:
          yield space instanceof RedSpace? "·" : "•";
        case BLACK:
          yield "Ω";
        case RED:
          yield "☺";
      });

      return String.format(
          "%s%s",
          strSpace,
          space.isRightEdge()
            ? "\n"
            : " ");
    })
    .reduce(new StringBuilder(), StringBuilder::append, (l, r) -> l)
    .toString();
}
```
That new `yield` keyword at work! I can essentially iterate over each `space` to yield a character.

_More examples can be found here at this [Baeldung article](https://www.baeldung.com/java-switch-pattern-matching)_.

# Issues

* google code formatter breaks. It looks like it breaks on records, multi-line strings, and the `yield` keyword that occurs in the switch expressions
  * To solve this, I think some motivated person could just start collaborating on Google GitHub repos for this. [The spec repo](/) [The configuration repo]
(/)
* the maven compiler (actually, the JVM) requires the `--enable-preview` flag to unlock the features. This is an obvious red flag for usage in production code. It is hard to assess the risk of using these features as well. In the past, many features of the JDK that shipped as previews in earlier versions of Java have actually functioned perfectly. Will that continue to be the case?

# Wrap-up

One cannot simply assume that `preview` Java feature will become integrated into the JDK long‑term.
The [JavaDoc for the `Record` class](https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/Record.html) even contains a disclaimer:

> This class is associated with _records_, a preview feature of the Java language. Programs can only use this class when preview features are enabled. Preview features may be removed in a future release, or upgraded to permanent features of the Java language.

In the case of `records`, however, you can breathe easy, as it is [officially integrated into Java 16](https://openjdk.java.net/jeps/395).

As for the other features described in this article:

| Feature | Java version introduced | Java version finalized |
|---|---|---|
| [Pattern Matching in `instanceof`](https://openjdk.java.net/jeps/394) | 14 | 16 |
| [Records](https://openjdk.java.net/jeps/395) | 14 | 16 |
| [Multi-line Strings](https://openjdk.java.net/jeps/378) | 13 | 14 |
| [Switch Expressions](https://openjdk.java.net/jeps/361) | 12 | 14 |
| [Sealed interfaces](https://openjdk.java.net/jeps/397) | 15 | ? |

Sealed interfaces are still not officially being integrated. Even if they stick around, their syntax is subject to change. So that isn't a feature we were able to use here yet.

The other features that I listed as being integrated for version 16, have not changed syntactically since 15, (as far as I could tell). So with those, you're in the clear

## Verdict

If you don't care about using the Google code formatter plugin for a while, and don't care about using sealed interfaces yet, you and your team can make the upgrade!

If for some reason you are still tempted to use the sealed interfaces anyways, I would strongly suggest not using them! It is not an officially integrated feature and could change or even go away.
A good example of this was the "Switch Expressions" feature. It was introduced in version 12 and overloaded the `break` keyword. This was changed to `yield` in version 13 to make more clear the intended behavior. Since it changed, it naturally needed to stay in preview in version 13, then finally was accepted in version 14. There's no telling what kind of iteration the sealed interfaces could still undergo.

---

# Complete side-note | Speaking of features coming and going or staying
Sometimes, it is nice to declare a `Function` in a Java Lambda, but you plan to return a specific object regardless of the input. In the past, I have used an underscore `_` to define the variable of non-interest. [Java 9 had discontinued](https://docs.oracle.com/en/java/javase/14/language/small-language-changes.html) this from being allowed.
Which I found interesting, considering that the introduction of `_` as a variable name, had the apparent intention of being used in [lambdas for unused arguments](https://openjdk.java.net/jeps/302).

While in our code bases, we have now switched to using double underscores `__` for this, we also use Vavr. So a nice alternative might be to use [`Function1.constant`](https://javadoc.io/doc/io.vavr/vavr/0.10.3/io/vavr/Function1.html#constant-R-).