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
andString.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));