JDK 10 and the New Release Cadence
Released in March 2018, JDK 10 is the first release under Java’s 6-month release cadence. Previously, Java delivered major updates every 2-3 years, but starting with JDK 10, regular releases occur every 6 months. However, not every release is LTS (Long-Term Support), and JDK 10 is a non-LTS version.
The key idea behind this release model is to “ship small features quickly.” Instead of delaying an entire release until a large feature is ready, features that are ready are shipped in 6-month increments.
| Release Model | Cadence | Support Period |
|---|---|---|
| LTS (11, 17, 21) | Every 2 years | At least 8 years |
| non-LTS (10, 12, 13…) | Every 6 months | Until the next release (6 months) |
var — Local Variable Type Inference
The most notable change in JDK 10 is the var keyword (JEP 286). When declaring local variables, the compiler infers the type from the right-hand side, reducing repetitive type declarations.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class VarExample {
public static void main(String[] args) {
// Traditional approach: specifying the type twice
HashMap<String, List<Integer>> oldMap = new HashMap<String, List<Integer>>();
// Using var: compiler infers the type from the right-hand side
var scores = new HashMap<String, List<Integer>>();
scores.put("Math", List.of(95, 88, 92));
scores.put("English", List.of(85, 90, 78));
// var shines with complex generic types
var averages = scores.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0.0)
));
System.out.println("Averages by subject: " + averages);
// Output: Averages by subject: {Math=91.66666666666667, English=84.33333333333333}
// Can also be used in for loops
for (var entry : scores.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// Output:
// Math: [95, 88, 92]
// English: [85, 90, 78]
// Can also be used with try-with-resources
var list = new ArrayList<String>();
list.add("Java");
list.add("Kotlin");
System.out.println("Languages: " + list);
// Output: Languages: [Java, Kotlin]
}
}
var Usage Guidelines
While var is convenient, overusing it can harm readability. The following guidelines are based on Oracle’s official style guide.
Good use cases:
- When the type is obvious from the right-hand side:
var list = new ArrayList<String>() - When generics are complex:
var map = new HashMap<String, List<Map<String, Integer>>>() - For for-each and try-with-resources variables
Cases to avoid:
- When the type cannot be determined from the right-hand side alone:
var result = getResult()— unclear what type it is - With primitive literals:
var count = 0— ambiguous whether it is int or long
public class VarGuideline {
public static void main(String[] args) {
// Good: type is obvious from the right-hand side
var names = new ArrayList<String>();
var count = names.size(); // Clearly returns int
// Bad: type is unclear (explicit type declaration is recommended in such cases)
// var data = fetchData(); // What type? List? Map? String?
// var x = 0; // int? long? short?
// var can only be used for local variables
// It cannot be used for fields, method parameters, or return types
System.out.println("var is a keyword for local variables only.");
// Output: var is a keyword for local variables only.
}
}
var is not a reserved word but a reserved type name. This means you cannot declare a variable named var, but methods or packages named var are allowed.
Optional.orElseThrow()
JDK 8’s Optional.get() throws NoSuchElementException when the value is absent, but the method name alone does not suggest that an exception could occur, causing confusion. The orElseThrow() method added in JDK 10 has the same behavior but with a name that clearly expresses the intent.
import java.util.Optional;
public class OrElseThrowExample {
public static void main(String[] args) {
Optional<String> present = Optional.of("Hello");
Optional<String> empty = Optional.empty();
// JDK 8: get() — ambiguous name
String value1 = present.get();
System.out.println("get(): " + value1);
// Output: get(): Hello
// JDK 10: orElseThrow() — intent is clear
String value2 = present.orElseThrow();
System.out.println("orElseThrow(): " + value2);
// Output: orElseThrow(): Hello
// Calling on an empty Optional throws NoSuchElementException
try {
empty.orElseThrow();
} catch (java.util.NoSuchElementException e) {
System.out.println("Exception thrown: " + e.getMessage());
// Output: Exception thrown: No value present
}
}
}
Unmodifiable Collections
JDK 10 added copyOf() for creating immutable copies of existing collections, and toUnmodifiable* collectors for collecting stream results into immutable collections.
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class UnmodifiableCollections {
public static void main(String[] args) {
// List.copyOf() — create an immutable copy of an existing list
var mutable = new ArrayList<>(List.of("A", "B", "C"));
var immutable = List.copyOf(mutable);
mutable.add("D"); // The original can still be modified
System.out.println("Original: " + mutable);
// Output: Original: [A, B, C, D]
System.out.println("Copy: " + immutable);
// Output: Copy: [A, B, C] — unaffected by changes to the original
try {
immutable.add("E");
} catch (UnsupportedOperationException e) {
System.out.println("Immutable copy cannot be modified!");
// Output: Immutable copy cannot be modified!
}
// Collectors.toUnmodifiableList() — collect stream results into an immutable list
var numbers = Stream.of(3, 1, 4, 1, 5, 9)
.sorted()
.collect(Collectors.toUnmodifiableList());
System.out.println("Sorted immutable list: " + numbers);
// Output: Sorted immutable list: [1, 1, 3, 4, 5, 9]
// Collectors.toUnmodifiableMap()
var lengthMap = List.of("Java", "Go", "Rust").stream()
.collect(Collectors.toUnmodifiableMap(
s -> s,
String::length
));
System.out.println("String length map: " + lengthMap);
// Output: String length map: {Java=4, Go=2, Rust=4}
}
}
List.copyOf() does not create a copy if the original is already an immutable list — it simply returns the original as-is. This is an optimization to avoid unnecessary memory allocation.
G1 GC Parallel Full GC
G1, which became the default GC in JDK 9, is designed to avoid Full GC in most cases. However, when mixed GC cannot keep up with memory reclamation, a Full GC occurs. Until JDK 9, G1’s Full GC ran on a single thread, causing severe latency.
In JDK 10 (JEP 307), G1’s Full GC was changed to run in parallel. The number of threads can be configured with the -XX:ParallelGCThreads option, significantly reducing Stop-the-World time when a Full GC occurs.
Application Class-Data Sharing
CDS (Class-Data Sharing) is a technology that saves class metadata loaded during JVM startup to an archive file and reuses it on subsequent startups to reduce startup time. In JDK 10 (JEP 310), this feature was extended to include application classes.
# Step 1: Generate class list
java -Xshare:off -XX:DumpLoadedClassList=classes.lst -jar myapp.jar
# Step 2: Create archive
java -Xshare:dump -XX:SharedClassListFile=classes.lst -XX:SharedArchiveFile=app-cds.jsa -jar myapp.jar
# Step 3: Use archive for faster startup
java -Xshare:on -XX:SharedArchiveFile=app-cds.jsa -jar myapp.jar
This is especially beneficial in environments like microservices where multiple instances of the same application are launched, providing both memory savings and reduced startup time.
Summary
| Feature | Core Value |
|---|---|
| var | Eliminates verbose type declarations, improves readability of generic code |
| G1 Parallel Full GC | Reduces Stop-the-World time during Full GC |
| Application CDS | Reduces JVM startup time and saves memory |
| Optional.orElseThrow() | More intention-revealing API than get() |
| Unmodifiable Collections | Defensive copying and immutable collection in a single line |
| 6-month release cadence | Small features shipped quickly, LTS versions remain stable |
Although JDK 10 is a non-LTS release, var and Unmodifiable Collections are features used daily in every subsequent Java version. For var, the simple rule to remember is “don’t write the type twice.” However, if the type is not obvious from the right-hand side, keeping the explicit type declaration is a wise choice for the overall readability of your team’s codebase.