java from 8 to 16

Java 16 is released and it’s time to leave our good ol’ Java 8 and begin to use the new language features Java brings with its versions.

In this post, I will only cover some of the newest Java features.

Java 9

Release notes

Private methods in interfaces

interface PetStore {
  // You can now define private methods in interfaces. It's quite useful if you
  // want to factorize some code from default methods
  private long getNumberAvailablePets(List<Pet> pets, Predicate<Pet> predicate) {
    return pets.stream()
        .filter(Pet::isAvailable)
        .filter(predicate)
        .count();
  }
 
  default long getNumberAvailableCats(List<Pet> pets) {
    return getNumberAvailablePets(pets, pet -> Pet.Type.CAT == pet.getType());
  }
 
  default long getNumberAvailableDogs(List<Pet> pets) {
    return getNumberAvailablePets(pets, pet -> Pet.Type.DOG == pet.getType());
  }
}

New Optional APIs

class NewOptionalAPIs {
 
  private static final List<Cat> CATS = Arrays.asList(
      new Cat("Tadar sauce"),
      new Cat("Nyan cat"),
      new Cat("Grumpy cat")
  );
 
  private static Optional<Cat> findByName(String name) {
    return CATS.stream().filter(c -> name.equals(c.getName())).findAny();
  }
 
  public static void main(String[] args) {
    /*
     * Optional.ifPresentOrElse
     */
    Optional<Cat> cat = findByName("Grumpy cat");
    // Before
    if (cat.isPresent()) {
      System.out.println(cat.get().getName());
    } else {
      System.out.println("No cat found");
    }
    // After
    cat.ifPresentOrElse(c -> System.out.println(c.getName()), () -> System.out.println("No cat found"));
    findByName("foobar").ifPresentOrElse(c -> System.out.println(c.getName()), () -> System.out.println("No cat found"));
 
    /*
     * Optional.isEmpty
     */
     // Before
    if (cat.isEmpty()) {
      System.out.println("No cat found");
    } else {
      System.out.println(cat.get().getName());
    }
 
    // After
    // Optional.or
    Optional<Cat> c = findByName("Garfield")
        .or(() -> findByName("Tata"))
        .or(() -> findByName("Toto"));
    System.out.println(c.orElse(new Cat("titi")));
 
    // Optional.stream
    String name = "Tom";
    Cat[] cats = Optional.of(new Cat(name)).stream().toArray(Cat[]::new);
    System.out.println(cats);
  }
}

New Future APIs

// Annotation @Deprecated now has the attributes "forRemoval" and "since", useful to document Java classes
@Deprecated(forRemoval = true, since = "0.1.0")
public class NewCompletableFutureAPIs {
 
  public static void main(String[] args) throws InterruptedException, ExecutionException {
    CompletableFuture<String> future = new CompletableFuture<>();
    // Throw a TimeoutException if the task is not finished 
    future.orTimeout(2, TimeUnit.SECONDS);
    // Completes with the given value upon timeout
//  future.completeOnTimeout("foobar", 2, TimeUnit.SECONDS);
 
    Runnable runnable = () -> {
      try {
        Thread.sleep(3000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      future.complete("finished");
    };
    runnable.run();
    System.out.println(future.get());
  }
}

New Collection APIs

public static void main(String[] args) {
  /*
   * Set.of / List.of
   */
  // Before
  Set<String> catNames = new HashSet<>();
  catNames.add("Tadar sauce");
  catNames.add("Grumpy cat");
  catNames.add("Nyan cat");
  System.out.println(Collections.unmodifiableSet(catNames));
 
  // After
  System.out.println(Set.of("Tadar sauce", "Grumpy cat", "Nyan cat"));
 
  /*
   * Map.of
   */
  // Before
  Map<String, Integer> catLikes = new HashMap<>();
  catLikes.put("Tadar sauce", 10);
  catLikes.put("Grumpy cat", 1);
  catLikes.put("Nyan cat", 1000);
  System.out.println(Collections.unmodifiableMap(catLikes));
 
  // After
  System.out.println(Map.of("Tadar sauce", 10, "Grumpy cat", 1, "Nyan cat", 1000));
}

JShell

$ ${JAVA_HOME}/bin/jshell
|  Welcome to JShell -- Version 14
|  For an introduction type: /help intro
 
jshell> String greeting = "Hello, world";
greeting ==> "Hello, world"
 
jshell> System.out.println(greeting);
Hello, world
 
jshell> /vars
|    String greeting = "Hello, world"
 
jshell> 2+2*4
$3 ==> 10

Jigsaw

Java 9 also brings the Project Jigsaw whose goal is to have a standard module system for the Java Platform.

This is a large topic that I will not cover here.

Java 10

Release notes

Inference type

public class InferenceType {
  // Does not compile
//  private final var foobar = new String();
 
  public static void main(String[] args) {
    var catName = "Grumpy cat"; // String
    var nbCat = Integer.valueOf(1); // int
    var isCute = Boolean.TRUE; // Boolean
 
    System.out.println("There are " + nbCat + " " + catName);
 
    for (var i = 0; i < 10; i++) {
      System.out.println(i);
    }
 
    var catNames = new ArrayList<String>(); // Type resolved
    // Does not compile
//    catNames = new LinkedList<String>();
 
    // Does not compile
//    var foo;
//    var ints = {0, 1};
//    var appendSpace = (String a) -> a + " ";
  }
 
  // Does not compile
//  private var doSomething() {
//    return "foobar";
//  }
 
  // Does not compile
//  private String foobar(var s) {
//    return "";
//  }
}

New Optional APIs

public static void main(String[] args) {
  Optional.ofNullable(null).orElseThrow();
}

New Collection APIs

public static void main(String[] args) {
  // List.copyOf / Set.copyOf
  List.copyOf(List.of("foo", "bar"))
      .stream()
      .collect(
          // Collectors.toUnmodifiableList() / Collectors.toUnmodifiableSet
          Collectors.toUnmodifiableList()
      );
  // Map.copyOf
  Map.copyOf(Map.of("foo", "bar"));
}

Java 11

Release notes

Inference type on lambda

public class NewInferenceType {
  static IntFunction<Integer> doubleIt1 = x -> x * 2;
  // Compiles on java 8
  static IntFunction<Integer> doubleIt2 = (@Deprecated int x) -> x * 2;
  // Compiles only from java 11
  // Useful if we want to add annotations to the parameter
  static IntFunction<Integer> doubleIt3 = (@Deprecated var x) -> x * 2;
}

New String APIs

// String.isBlank
System.out.println(" ".isBlank());
System.out.println("".isBlank());
System.out.println("foo".isBlank());
 
// String.lines
"foo\nbar".lines().forEach(System.out::println);
 
// String.strip ~= String.trim with "Unicode aware"
System.out.println("foo" + " azert ".strip() + "bar");
System.out.println("foo" + " azert ".stripLeading() + "bar");
System.out.println("foo" + " azert ".stripTrailing() + "bar");
 
// String.repeat
System.out.println("foobar".repeat(3));

New Files APIs

// Files
var path = Files.writeString(Files.createTempFile("foobar", ".txt"), "foobar");
System.out.println(path);
System.out.println(Files.readString(path));

New HttpClient APIs

// HttpClient
var httpClient = HttpClient.newBuilder()
    .version(HttpClient.Version.HTTP_2)
    .build();
var request = HttpRequest.newBuilder()
    .uri(URI.create("http://localhost:8080/actuator/health"))
    .GET()
    .build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
System.out.println();
 
var future = httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
    .thenAccept(r -> {
      System.out.println(r.statusCode());
      System.out.println(r.body());
      r.body();
    });
 
future.get();

Java 12

Release notes

New Switch expressions

// Before
switch (day) {
  case MONDAY:
  case FRIDAY:
  case SUNDAY:
    System.out.println(6);
    break;
  case TUESDAY:
    System.out.println(7);
    break;
  case THURSDAY:
  case SATURDAY:
    System.out.println(8);
    break;
  case WEDNESDAY:
    System.out.println(9);
    break;
}
// After
switch (day) {
  case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
  case TUESDAY                -> System.out.println(7);
  case THURSDAY, SATURDAY     -> System.out.println(8);
  case WEDNESDAY              -> System.out.println(9);
}
 
// Can be used as an expression
var d = switch (day) {
  case MONDAY, FRIDAY, SUNDAY -> 6;
  case TUESDAY                -> 7;
  case THURSDAY, SATURDAY     -> 8;
  case WEDNESDAY              -> 9;
  default                     -> 10;
};

Java 13

Release notes

Switch expressions enhancements

// yield keyword used when assigning / returning value
// and when we need to have multiple instructions in a block
var d = switch (day) {
  case MONDAY, FRIDAY, SUNDAY: yield 6;
  case TUESDAY               : yield 7;
  case THURSDAY, SATURDAY    : yield 8;
  case WEDNESDAY             : yield 9;
  default                    :
    // yield useful here compared to the Java 12 arrow "->"
    System.out.println("In default case");
    yield 10;
};

To have this feature, you need to add a flag to enable it:

# when compiling
javac --release 13 --enable-preview YourJavaClass.java
# when running
java --enable-preview YourJavaClass.java

Text blocks

Finally…

// Before
var html = "<html>\n" +
           "  <body>\n" +
           "    <h1>Hello, world!</h1>\n" +
           "  </body>\n" +
           "</html>\n";
// After
var html = """
<html>
  <body>
    <h1>Hello, world!</h1>
  </body>
</html>
""";

Java 14

Release notes

Pattern matching for instanceof

// Before
if (obj instanceof String) {
  String s = (String) obj;
  if (!s.isBlank()) {
    System.out.println(s.repeat(3));
  }
}
// After
if (obj instanceof String s && !s.isBlank()) {
  System.out.println(s.repeat(3));
}

Records

// Lightweight class intended to carry data only (same as "data" class in Kotlin)
public record Cat(
  // Compact constructor to avoid writing verbose code (e.g. getters / setters)
  String name,
  String type,
  int age
){
  // No explicit parameter in constructor
  public Cat{
    // Can be used to validate inputs
    if (age < 0) {
      throw new IllegalArgumentException("Don't try to challenge time!");
    }
  }
 
  // Can create methods, however, records only have immutable data
  public void meow() {
    System.out.println("Meow! :3");
  }
}

Helpful NullPointerExceptions

public record Pet(PetId petId) {
    public static void main(String[] args) {
        Pet pet = new Pet(null);
        // will log the following:
        // Exception in thread "main" java.lang.NullPointerException: Cannot invoke "lin.louis.demo.PetId.value()" because the return value of "lin.louis.demo.Pet.petId()" is null
	at lin.louis.clean.domain.exception.Pet.main(Pet.java:8)
        System.out.println(pet.petId().value());
    }
}

JPackage

# create a deb installer file to install the application without needing to install Java
jpackage --input target/ \
  --name PetStoreApp \
  --main-jar PetStoreApp.jar \
  --main-class lin.louis.PetStoreApp \
  --type deb \
  --java-options '--enable-preview'

Java 15

Release notes

Sealed classes

// fine-grained control over inheritance
public abstract sealed class Pet permits Cat, Dog {
  // ...
}
 
// extending sealed class must itself be declared as "sealed", "non-sealed" or "final"
public final class Cat extends Pet {}
 
public non-sealed class Dog extends Pet {}

Java 16

Release notes