JEP 532: Primitive Types in Patterns, instanceof, and switch (Fifth Preview)
Introduction
Since JDK 16, Java's pattern matching has evolved into a powerful and elegant language mechanism. But it was always missing one piece: primitive types. JEP 532 fills that last gap, turning pattern matching from something that "only serves reference types" into a genuinely uniform system that covers every Java type. And the way it does so happens to eliminate an entire class of subtle precision-loss bugs.
The gap in pattern matching
Java's pattern matching began with instanceof patterns in JDK 16 and kept evolving: type patterns, record patterns, switch patterns, growing steadily more capable. But there was always one conspicuous gap in the system: primitives.
- Type patterns, record patterns, and switch patterns all cover only reference types.
- Primitives were entirely excluded: no
instanceof int, nocase int i. - More awkwardly, record components of primitive type could not be matched precisely. Deconstructing a record with an
intfield, pattern matching was of no help.
The result: the moment numbers were involved, developers were forced back to the old ways, manual range checks and explicit casts. The conciseness pattern matching was supposed to bring evaporated in numeric code, replaced by scattered casts and if checks.
What the JEP adds
JEP 532 formally brings primitives into the pattern-matching landscape, making it a complete system:
- Primitive type patterns everywhere patterns are allowed.
instanceofworks with primitive types, as a safe, value-based test.switchworks over any primitive type, includinglong,float,double, andboolean, no longer limited tointandString.- Record deconstruction can bind primitive components directly.
From now on, pattern matching is no longer a reference-type-only tool but a single language uniform across references and primitives.
The key idea: exactness
The entire design of this feature revolves around one core concept: exactness.
When you write x instanceof byte b, the compiler does not simply "truncate the value to a byte." It asks a more serious question: does this value fit into a byte without loss?
- Widening that loses no information succeeds. For example, matching a value that fits in a byte as a byte.
- A lossy narrowing fails to match. If a value falls outside the range a byte can represent, the match simply does not hold.
The elegance of this design is that it turns conversions that used to happen silently into explicit, checkable operations. In the past, a mistaken cast would quietly drop high bits and lose precision, and the compiler gave you no warning; now the same situation becomes a clean "failed match" rather than a bug lurking deep in the code.
Worth noting: unconditionally exact conversions still compile to a plain widening, introducing no extra overhead. The exactness check only kicks in where loss is genuinely possible.
Code example
switch over primitives:
// switch over long
String classify(long v) {
return switch (v) {
case 0L -> "zero";
case long pos if pos > 0 -> "positive";
default -> "negative";
};
}
// instanceof with primitives
if (x instanceof byte b) {
// x fits in a byte without loss
}
The first example switches over a long, something that was simply not allowed before. It even uses a guarded pattern, case long pos if pos > 0.
The second example uses instanceof to check whether a value fits losslessly into a byte. This is not a simple type check but an exactness check at the value level: the branch is entered only when x genuinely falls within the byte range.
Before and after
Before:
- Manual range checks before each narrowing cast.
switchlimited to int-like types, plusStringand enums.- Silent precision loss whenever a cast was wrong, with no help from the compiler.
After:
- One uniform pattern language for all types.
switchoverlong,double,boolean, and the rest.- Lossy conversions become failed matches, not silent bugs.
Preview status
This is already the fifth preview, a meaningful signal: it means the API design is quite stable and finalization should not be far off.
- It ships in JDK 27 as a fifth preview, continuing the JDK 26 language design.
- It requires the
--enable-previewflag at both compile and run time. - It is part of Project Amber, the same umbrella that gave us records and sealed types.
Conclusion
JEP 532 finally makes pattern matching work across all Java types. One matching model, uniform across references and primitives; code that is not only cleaner but also safer thanks to the introduction of exactness. What it adds is not just a syntactic missing piece but a transformation of an entire class of silent precision-loss bugs into visible, compile-time-or-runtime "failed matches."