Skip to main content

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, no case int i.
  • More awkwardly, record components of primitive type could not be matched precisely. Deconstructing a record with an int field, 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.
  • instanceof works with primitive types, as a safe, value-based test.
  • switch works over any primitive type, including long, float, double, and boolean, no longer limited to int and String.
  • 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.
  • switch limited to int-like types, plus String and enums.
  • Silent precision loss whenever a cast was wrong, with no help from the compiler.

After:

  • One uniform pattern language for all types.
  • switch over long, 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-preview flag 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."