Definition
- Primitive patterns –
instanceofandswitchnow match against primitive types, not just reference types. - Safe narrowing –
if (longVal instanceof int i)succeeds only if the long value fits in an int without data loss. - Switch on long/double –
switchcan now match onlong,float, anddoublevalues, not justint,String, andenum. - Guards –
case int i when i > 0 -> ...combines type matching with conditions on primitive values. - Unification – completes the pattern matching model so it covers both reference and primitive types uniformly.
Before, the bank's routing system could only sort by customer name (reference types). After JEP 507, it can also sort by account number (int), balance (double), and transaction amount (long) directly in the routing rules.
Understanding the Concepts
What are Primitive Types in Patterns?
Primitive Types in Patterns (JEP 507, preview in Java 24) extends pattern matching so that instanceof and switch can match against primitive types and perform safe, range-checked conversions.
How does safe narrowing work?
When you write if (longVal instanceof int i), the pattern matches only if the long value fits within the int range (-2,147,483,648 to 2,147,483,647). If the value is outside that range, the pattern does not match and the if body is skipped.
package com.bank.service;
public class AmountClassifier {
public void classify(long amount) {
if (amount instanceof int smallAmount) {
System.out.println("Fits in int: " + smallAmount);
} else {
System.out.println("Too large for int: " + amount);
}
}
}
classify(50000) → "Fits in int: 50000" classify(3000000000L) → "Too large for int: 3000000000"
How does switch on primitives work?
public String classifyAmount(long amount) {
return switch (amount) {
case int i when i < 0 -> "Negative: " + i;
case int i when i <= 100000 -> "Small transaction: " + i;
case int i -> "Medium transaction: " + i;
case long l -> "Large transaction: " + l;
};
}
How is this different from manual range checking?
// BEFORE: manual range check and cast
public void store(long amount) {
if (amount >= Integer.MIN_VALUE && amount <= Integer.MAX_VALUE) {
int intAmount = (int) amount; // safe cast
storeAsInt(intAmount);
} else {
storeAsLong(amount);
}
}
// AFTER: primitive pattern (JEP 507)
public void store(long amount) {
if (amount instanceof int intAmount) {
storeAsInt(intAmount); // safe: guaranteed to fit
} else {
storeAsLong(amount);
}
}
What conversions are supported?
| Source | Pattern | Matches when |
|---|---|---|
| long | int i | Value fits in int range |
| double | float f | Value fits in float without precision loss |
| long | byte b | Value fits in byte range (-128 to 127) |
| int | short s | Value fits in short range |
What are common mistakes?
// WRONG: assuming all long values match int pattern
long bigAmount = 5_000_000_000L;
if (bigAmount instanceof int i) {
// This block is NEVER entered for 5 billion
System.out.println(i);
}
// Pattern matching is SAFE: no ClassCastException, no overflow
What should a senior engineer know?
- Primitive patterns complete the unification of pattern matching across all Java types.
- Combined with sealed types and record patterns, you can now write exhaustive switches that handle both reference and primitive cases.
- The JIT can optimize primitive pattern matches very efficiently since they reduce to range checks.
- This feature is preview in Java 24; the API may change.
What are common interview follow-up questions?
- Can you use primitive patterns in record deconstruction? (Yes, if a record component is a primitive, the pattern can narrow it.)
- Does this replace manual casting? (For safe narrowing, yes. The pattern only matches when the cast would be lossless.)
- What happens with floating point? (double instanceof float matches only if no precision is lost.)
Visual Overview
Primitive Pattern Matching:
long amount = 50000L;
switch (amount) {
case int i when i < 0 → "Negative"
case int i when i <= 100000 → "Small" ◀── matches (50000 fits in int)
case int i → "Medium"
case long l → "Large"
}
long bigAmount = 5_000_000_000L;
switch (bigAmount) {
case int i when i < 0 → skip (doesn't fit)
case int i when i <= 100000 → skip (doesn't fit)
case int i → skip (doesn't fit)
case long l → "Large" ◀── matches
}
Key Takeaway
Primitive Types in Patterns (JEP 507, Java 24 preview) extends pattern matching to primitive types. You can now write if (longVal instanceof int i) for safe narrowing that only matches when the value fits without data loss. Switch expressions can match on long, float, and double with guards, not just int, String, and enum.
This eliminates manual range checks and explicit casts. Before JEP 507, checking if a long fits in an int required amount >= Integer.MIN_VALUE && amount <= Integer.MAX_VALUE followed by a cast. Now a single pattern match does both the check and the binding.
The feature completes the pattern matching story: reference patterns (Java 16-21) handle objects and records, and primitive patterns (Java 24) handle numeric types. Together, they give Java a unified pattern matching model across all types.
Remember one sentence: primitive patterns extend instanceof and switch to match int, long, and double with safe narrowing, completing the pattern matching unification.
---