Java 10: Local-Variable Type Inference with var
Java 10 was a focused release, best known for introducing the var keyword — local-variable type inference. While small in scope, var meaningfully reduced verbosity in everyday Java code. This part covers var in depth, along with the other improvements in Java 10.
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 typed — var 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 everywhereString message = "Hello, Java 10!";List<String> names = new ArrayList<>();Map<String, List<Integer>> grouped = new HashMap<>();
// Java 10 — let the compiler infervar 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 usesvar 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 usesvar x; // no initialiservar nothing = null; // cannot infer from nullpublic var field = "hello"; // fields not allowedpublic var compute() { ... } // return types not allowedvoid process(var input) { ... } // parameters not allowedWhere 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 verboselyMap<String, Map<Integer, List<String>>> registry = new HashMap<String, Map<Integer, List<String>>>();
// After — type is clear from the right sidevar registry = new HashMap<String, Map<Integer, List<String>>>();Iterator Patterns
// BeforeIterator<Map.Entry<String, Integer>> it = scores.entrySet().iterator();
// Aftervar 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 explicitProcessResult x = getResult();
// ❌ Primitive widening can surprise youvar x = 1.0f; // float, not double — easy to missThe 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 UnsupportedOperationExceptionCollectors.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 NoSuchElementExceptionApplication 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.
| Feature | Key Benefit |
|---|---|
var (local-variable type inference) | Less boilerplate; compiler infers type from initialiser |
List/Set/Map.copyOf() | Convenient immutable collection snapshots |
Collectors.toUnmodifiableList/Set/Map | Stream pipelines that produce immutable results |
Optional.orElseThrow() | More expressive than get() for mandatory values |
| Application Class-Data Sharing | Faster startup, lower memory in multi-JVM deployments |