Igor's Techno Club

Migrating from Java 8 to Java 17 II: Notable API Changes

This is a continuation of my previous post:

Migrating from Java 8 to Java 17: A Comprehensive Guide to New Features


Java has seen a great evolution from version 8 to version 17, with multiple enhancements and new quality-of-life improvements. This article takes a deeper dive into the API changes and additions introduced in later Java releases which I found most interesting and useful for those who migrating from Java 8.

Java 9

Java 9 marked a significant overhaul in the Java ecosystem with the introduction of the module system, but it also brought several valuable API enhancements:

Objects.requireNonNullElse

This method helps streamline handling null values, removing the need for verbose null checks.

Java 8:

String value = (str != null) ? str : "default";

Java 9 :

String value = Objects.requireNonNullElse(str, "default");

Collection Factory Methods

Convenient factory methods for creating immutable collections were introduced in Java 9.

Java 8:

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
List<String> unmodifiableList = Collections.unmodifiableList(list);

Java 9 :

List<String> list = List.of("a", "b", "c");

Enhanced Stream API

Added methods like takeWhile(), dropWhile(), and iterate() with a predicate to the Stream API for more functional-style operations on streams.

Java 9 :

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
List<Integer> result = numbers.stream()
                              .takeWhile(n -> n < 4)
                              .collect(Collectors.toList());

CompletableFuture.delayedExecutor()

Introduced to facilitate scheduling of tasks with a delay.

Java 9 :

Executor executor = CompletableFuture.delayedExecutor(5, TimeUnit.SECONDS);

Collectors.flatMapping()

Added to enable flat-mapping operations within collectors, useful for complex reductions.

Java 9 :

List<List<String>> listOfLists = List.of(List.of("a", "b"), List.of("c", "d"));
List<String> flattenedList = listOfLists.stream()
    .collect(Collectors.flatMapping(List::stream, Collectors.toList()));

Optional Enhancements

New methods like ifPresentOrElse() improved the usability and flexibility of the Optional class.

Java 9 :

Optional<String> optional = Optional.of("Hello");

optional.ifPresentOrElse(
    value -> System.out.println("Value: " + value),
    () -> System.out.println("No value present")
);

java.util.concurrent.Flow

Introduced to support reactive streams.

Java 9 :

import java.util.concurrent.Flow;

Java 10

Java 10 continued the trend of incremental improvements, focusing more on performance and usability tweaks:

Local-Variable Type Inference (var)

Though not an API change, the var keyword allows for more concise variable declarations and improved readability.

Java 10 :

var list = new ArrayList<Map<String, List<Integer>>>();

Unmodifiable Collections

Introduced methods like List.copyOf(), Set.copyOf(), and Map.copyOf() for creating unmodifiable copies of collections.

Java 8:

List<String> list = new ArrayList<>();
list.add("a");
List<String> unmodifiableList = Collections.unmodifiableList(list);

Java 10 :

List<String> list = List.copyOf(originalList);

Optional Enhancements

Added orElseThrow() method without parameters.

Java 10 :

Optional<String> optional = Optional.empty();
String value = optional.orElseThrow(); // Throws NoSuchElementException

For more content like this, subscribe to the blog



Java 11

Java 11 brought several noteworthy API enhancements, particularly for String manipulation and InputStream handling:

String Enhancements

The String class received the repeat(int) method for easily repeating strings.

Java 8:

String repeated = String.join("", Collections.nCopies(3, "abc"))

Java 11 :

String repeated = "abc".repeat(3);

InputStream Enhancements

Introduced readNBytes(int) for more controlled reading of bytes from an InputStream.

Java 8:

byte[] buffer = new byte[10];
int bytesRead = inputStream.read(buffer, 0, 10);

Java 11 :

byte[] buffer = inputStream.readNBytes(10);

New Files Methods

Added writeString() and readString() methods for simpler file I/O operations.

Java 8:

Files.write(Paths.get("file.txt"), "Hello, World!".getBytes(StandardCharsets.UTF_8));
String content = new String(Files.readAllBytes(Paths.get("file.txt")), StandardCharsets.UTF_8);

Java 11 :

Files.writeString(Path.of("file.txt"), "Hello, World!", StandardCharsets.UTF_8);
String content = Files.readString(Path.of("file.txt"), StandardCharsets.UTF_8);

Java 12

Single-File Source-Code Programs

Enabled the ability to run a single Java source file directly, simplifying the process for small and quick scripts and testing.

Java 8:

javac HelloWorld.java
java HelloWorld

Java 12 :

java HelloWorld.java

Switch Expressions (Preview)

Introduced multi-case labels for switch statements that can now also return values directly, eliminating the need for separate assignment statements.

Java 12 Example (Preview):

int numLetters = switch (day) {
    case "MONDAY", "FRIDAY", "SUNDAY":
        yield 6;
    case "TUESDAY":
        yield 7;
    case "THURSDAY", "SATURDAY":
        yield 8;
    case "WEDNESDAY":
        yield 9;
    default:
        throw new IllegalArgumentException("Unexpected day: " + day);
};

Java 14

Switch Expressions (Standard Feature)

The new arrow syntax for switch statements made the flow more concise and eliminated the need for fall-through semantics. This feature became standard in Java 14.

Java 8:

int numLetters;
switch (day) {
    case "MONDAY":
    case "FRIDAY":
    case "SUNDAY":
        numLetters = 6;
        break;
    case "TUESDAY":
        numLetters = 7;
        break;
    case "THURSDAY":
    case "SATURDAY":
        numLetters = 8;
        break;
    case "WEDNESDAY":
        numLetters = 9;
        break;
    default:
        throw new IllegalArgumentException("Unexpected day: " + day);
}

Java 14 :

int numLetters = switch (day) {
    case "MONDAY", "FRIDAY", "SUNDAY" -> 6;
    case "TUESDAY" -> 7;
    case "THURSDAY", "SATURDAY" -> 8;
    case "WEDNESDAY" -> 9;
    default -> throw new IllegalArgumentException("Unexpected day: " + day);
};

Java 15

Text Blocks

Officially graduated from preview, providing a clean way to handle multiline strings.

Java 15 :

String sqlQuery = """
                  SELECT id, name, age
                  FROM users
                  WHERE age > 30
                  ORDER BY age DESC
                  """;

String Formatted Methods

The String::formatted method was introduced for better string formatting capabilities.

Java 8:

String formatted = String.format("Name: %s, Age: %d", name, age);

There is another way to do string formatting: MessageFormat class. If you are seeing it for the first time, check out this article covering the main differences between MessageFormat and String.format

Java 15 :

String formatted = "Name: %s, Age: %d".formatted(name, age);

Java 16

Records

Introduced record classes to reduce boilerplate when creating simple data carrier classes.

Java 8:

public class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public boolean equals(Object o) { 
        // equality logic 
    }
    
    @Override
    public int hashCode() { 
        // hashcode logic 
    }
    
    @Override
    public String toString() { 
        // toString logic 
    }
}

Java 16 :

public record Person(String name, int age) {}

Pattern Matching for instanceof

Simplified casting by introducing pattern matching for instanceof.

Java 8:

if (obj instanceof String) {
    String str = (String) obj;
    // Use str
}

Java 16 :

if (obj instanceof String str) {
    // Use str directly
}

Stream.toList()

Added a convenient method to collect Stream elements directly into an unmodifiable List.

Java 8:

List<String> list = stream.collect(Collectors.toList());//mutable

Java 16 :

List<String> list = stream.toList(); //unmodifiable

Java 17

Hexadecimal Binary Data Representation

Java 17 provided utilities to present binary data in hexadecimal form, which is very useful for debugging purposes.

Java 17 :

byte[] data = new byte[] { 0x1A, 0x2B, 0x3C };
String hex = HexFormat.of().formatHex(data);
System.out.println(hex); // Outputs: 1a2b3c

ZoneId.ofOffset()

Added ZoneId.ofOffset() for creating Zone IDs directly from offsets.

Java 8:

ZoneId zone = ZoneId.of("UTC+01:00");

Java 17 :

ZoneId zone = ZoneId.ofOffset("UTC", ZoneOffset.ofHours(1));

#java