From Java 8 Stubbornness to Java 21: My Upgrade Journey and New Language Features
The article walks through the author's personal upgrade from Java 8 to Java 21, comparing preview features, pattern‑matching instanceof, switch expressions, text blocks, the new HTTP client, var inference, virtual threads, records, immutable collections, try‑with‑resources improvements and other enhancements, while rating each feature’s practicality.
Background
Oracle released JDK 8 in 2014, making it the mainstream Java version. Ten years later the platform has reached JDK 24, with JDK 8, 11, 17 and 21 being the long‑term support releases.
Preview Features
Preview features are fully designed and implemented but not permanent; they require compilation and runtime flags javac and java with --enable-preview. This allows the community to try and evaluate new capabilities before they become standard.
Pattern Matching for instanceof
Java 8
if (e instanceof MethodArgumentNotValidException) {
MethodArgumentNotValidException methodArgumentNotValidException = (MethodArgumentNotValidException) e;
BindingResult result = methodArgumentNotValidException.getBindingResult();
...
}Java 21
if (e instanceof MethodArgumentNotValidException methodArgumentNotValidException) {
BindingResult result = methodArgumentNotValidException.getBindingResult();
...
}The Java 21 version eliminates the explicit cast, making the code shorter and clearer. Introduced in JDK 14 (preview) and standardized in JDK 16. Practical rating: ★★★★★
Switch Expressions
Java 8
String dayType;
switch (day) {
case 1: case 2: case 3: case 4: case 5:
dayType = "Weekday"; break;
case 6: case 7:
dayType = "Weekend"; break;
default:
throw new IllegalArgumentException("Invalid day: " + day);
}Java 21
String dayType = switch (day) {
case 1, 2, 3, 4, 5 -> "Weekday";
case 6, 7 -> "Weekend";
default -> throw new IllegalArgumentException("Invalid day: " + day);
};The new form is more compact, returns a value directly and avoids missing break. Introduced in JDK 12 (preview) and standardized in JDK 14. Practical rating: ★★★★★
Pattern‑Matching Switch
Combines instanceof with a switch to handle multiple types.
Java 8
if (obj instanceof Integer) {
return String.format("int %d", obj);
} else if (obj instanceof Long) {
return String.format("long %d", obj);
} else if (obj instanceof Double) {
return String.format("double %f", obj);
} else if (obj instanceof String) {
return String.format("String %s", obj);
}
return obj.toString();Java 21
return switch (obj) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
case null -> "null";
default -> obj.toString();
};Pattern‑matching switch reduces boilerplate and handles null gracefully. Introduced in JDK 17 (preview) and standardized in JDK 21. Practical rating: ★★★★
Text Blocks
Multi‑line strings become readable without manual escaping.
Java 8
String jsonStr = "{
" +
" \"name\":\"张三\",
" +
" \"age\":18
" +
"}
";Java 21
String jsonStr = """
{
"name":"张三",
"age":38
}
""";Text blocks improve readability for JSON, SQL, HTML, etc. Introduced in JDK 13 (preview) and standardized in JDK 15. Practical rating: ★★★★★
HTTP Client API
Java 8
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("GET");
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
}Java 21
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofSeconds(10))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());The new API is fluent, supports sync/async, and includes built‑in timeout and retry. Standardized in JDK 11. Practical rating: ★★★★
Local‑Variable Type Inference ( var )
Java 8
Map<String, List<Employee>> employeeMap = new HashMap<String, List<Employee>>();
Iterator<Map.Entry<String, List<Employee>>> iterator = employeeMap.entrySet().iterator();Java 21
var employeeMap = new HashMap<String, List<Employee>>();
var iterator = employeeMap.entrySet().iterator(); varreduces boilerplate, especially with nested generics. Standardized in JDK 11. Practical rating: ★★★
Virtual Threads
Java 8
ExecutorService executor = Executors.newFixedThreadPool(200); // OS threads limit
Future<String> future = executor.submit(() -> httpClient.get(url));Java 21
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<String> future = executor.submit(() -> httpClient.get(url));
}Virtual threads are lightweight, many can share a single OS thread, dramatically improving throughput. Previewed in JDK 19, standardized in JDK 21. Practical rating: ★★★★★
Record Classes
Java 8
public class Employee {
private final String name;
private final String department;
private final int salary;
// constructors, getters, equals, hashCode, toString → >50 lines of boilerplate
}Java 21
public record Employee(String name, String department, int salary) {}Records automatically generate accessor methods and immutable semantics. Previewed in JDK 14, standardized in JDK 16. Practical rating: ★★★
Quick Creation of Immutable Collections
Java 8
List<String> list = Collections.unmodifiableList(Arrays.asList("a", "b"));Java 21
List<String> list = List.of("a", "b", "c");
Set<String> set = Set.of("a", "b", "c");
Map<String, Integer> map = Map.of("a", 1, "b", 2);Factory methods provide concise immutable collections without external libraries. Standardized in JDK 9. Practical rating: ★★★
Try‑With‑Resources Improvements
Java 8
try (FileInputStream fis = new FileInputStream("");
FileOutputStream fos = new FileOutputStream("")) {
// ...
} catch (IOException e) {
e.printStackTrace();
}Java 21
FileInputStream fis = new FileInputStream("");
FileOutputStream fos = new FileOutputStream("");
// multiple resources separated by semicolon
try (fis; fos) {
// ...
} catch (IOException e) {
e.printStackTrace();
}The new syntax makes multi‑resource handling cleaner. Standardized in JDK 9. Practical rating: ★★★★
Sequenced Collections
Java 21 adds methods to directly access the first and last elements.
Java 8
List<Integer> list = new ArrayList<>();
if (!list.isEmpty()) {
Integer first = list.get(0);
Integer last = list.get(list.size() - 1);
}Java 21
List<Integer> list = new ArrayList<>();
if (!list.isEmpty()) {
Integer first = list.getFirst();
Integer last = list.getLast();
}Introduced in JDK 21. Practical rating: ★★★★
Other New Features (brief)
Interface private methods
Enhancements to String, streams, Optional, Files Precise NullPointerException messages
Sealed classes
Method‑handle based reflection for better performance
Scoped Values (preview) as a superior alternative to ThreadLocal for virtual threads
Conclusion
Each JDK release focuses on developer productivity, performance and maintainability. A major theme is garbage‑collector improvement: G1 became default in JDK 9, ZGC arrived in JDK 11 with sub‑10 ms pauses and support for up to 16 TB heaps. Upgrading to Java 21 therefore brings not only language conveniences but also significant runtime gains.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Shepherd Advanced Notes
Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
