Java Evolution — Part 3

Java 10, released on March 20, 2018, was the first release under the new six-month cadence. It was deliberately scoped — a smaller release designed to prove the new process worked. Its headline feature, var, had an outsized impact on day-to-day Java code despite being a single keyword.


Local-Variable Type Inference: var

JEP 286 introduced the var keyword for local variable declarations. When you use var, the compiler infers the type from the initialiser expression. The variable is still statically typedvar is not dynamic typing. The compiler simply figures out the type so you do not have to write it explicitly.

// Before Java 10 — explicit types everywhere
String message = "Hello, Java 10!";
List<String> names = new ArrayList<>();
Map<String, List<Integer>> grouped = new HashMap<>();
// Java 10 — let the compiler infer
var message = "Hello, Java 10!";
var names = new ArrayList<String>();
var grouped = new HashMap<String, List<Integer>>();

The inferred type is exactly the same as what you would have written. There is no runtime overhead — var is purely a compile-time feature.


Where var Can Be Used

var is valid only for local variables with an initialiser. It cannot be used for:

  • Method parameters
  • Return types
  • Fields
  • Catch parameters (until Java 11 added that for lambda parameters)
// ✅ Valid uses
var count = 0;
var list = List.of("a", "b", "c");
var entry = map.entrySet().iterator().next();
for (var item : list) {
System.out.println(item);
}
try (var stream = Files.newInputStream(path)) {
// var in try-with-resources
}
// ❌ Invalid uses
var x; // no initialiser
var nothing = null; // cannot infer from null
public var field = "hello"; // fields not allowed
public var compute() { ... } // return types not allowed
void process(var input) { ... } // parameters not allowed

Where var Shines

The biggest wins come when the type on the right side of the assignment already makes the type obvious, and writing it again on the left adds nothing but noise.

Long Generic Types

// Before — type repeated verbosely
Map<String, Map<Integer, List<String>>> registry =
new HashMap<String, Map<Integer, List<String>>>();
// After — type is clear from the right side
var registry = new HashMap<String, Map<Integer, List<String>>>();

Iterator Patterns

// Before
Iterator<Map.Entry<String, Integer>> it = scores.entrySet().iterator();
// After
var it = scores.entrySet().iterator();

Complex Stream Pipelines

var result = employees.stream()
.filter(e -> e.salary() > 80_000)
.collect(Collectors.groupingBy(Employee::department));

Try-With-Resources

try (var conn = dataSource.getConnection();
var stmt = conn.prepareStatement(SQL);
var result = stmt.executeQuery()) {
// process result
}

When to Avoid var

var reduces clarity when the type is not obvious from the right-hand side:

// ❌ What type is this? You have to look up the return type of getResult()
var x = getResult();
// ✅ Better — type is explicit
ProcessResult x = getResult();
// ❌ Primitive widening can surprise you
var x = 1.0f; // float, not double — easy to miss

The guiding principle is: use var when it removes noise without removing information. If a reader would have to look up the type to understand the code, write it explicitly.


Other Java 10 Improvements

Unmodifiable Collection Copies

Java 10 added List.copyOf(), Set.copyOf(), and Map.copyOf() to create unmodifiable copies of existing collections:

List<String> original = new ArrayList<>(List.of("a", "b", "c"));
original.add("d");
List<String> copy = List.copyOf(original); // [a, b, c, d] — immutable snapshot
// copy.add("e"); // throws UnsupportedOperationException

Collectors.toUnmodifiableList/Set/Map

List<String> immutable = stream
.filter(s -> s.length() > 3)
.collect(Collectors.toUnmodifiableList());

Optional.orElseThrow()

Optional.orElseThrow() with no arguments was added as a more expressive alias for Optional.get() — it throws NoSuchElementException if the value is absent, but its name makes the intent clearer.

String value = Optional.of("hello").orElseThrow(); // "hello"
Optional.empty().orElseThrow(); // throws NoSuchElementException

Application Class-Data Sharing

Java 10 extended the existing Class-Data Sharing (CDS) feature to allow application classes (not just JDK classes) to be stored in a shared archive. This reduces startup time and memory footprint when multiple JVM instances run the same application — particularly useful in containerised microservice environments.


Summary

Java 10 was a small but practical release. var is the feature developers interact with every day, and when used with good judgment, it genuinely improves readability by eliminating redundant type declarations.

FeatureKey Benefit
var (local-variable type inference)Less boilerplate; compiler infers type from initialiser
List/Set/Map.copyOf()Convenient immutable collection snapshots
Collectors.toUnmodifiableList/Set/MapStream pipelines that produce immutable results
Optional.orElseThrow()More expressive than get() for mandatory values
Application Class-Data SharingFaster startup, lower memory in multi-JVM deployments