Junior 6 min · March 06, 2026

Pattern Matching in Java - CryptoPayment MatchException

MatchException: no case matched CryptoPayment due to missing sealed permits.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Pattern matching binds a variable in the same step as type check: shorter, safer code
  • instanceof pattern: if (obj instanceof String s) replaces if (obj instanceof String) + String s = (String) obj
  • Switch expressions with patterns allow complex dispatch in one expression, with guards (when) and exhaustive checks
  • Sealed classes restrict subtyping, enabling compiler-verified exhaustive switch—no more default branches needed
  • Performance: pattern matching often compiles to same bytecode as manual casts; no runtime overhead for simple patterns
  • Biggest production gotcha: forgetting sealed+permits on a class hierarchy breaks exhaustive checks, leading to MatchException at runtime
Plain-English First

Imagine you work at a post office sorting packages. Every package arrives in an unmarked box. Old-school you would pick up the box, shake it, read a label, set it down, pick it up again, and finally open it. Pattern matching is like having a magic scanner that reads the label AND opens the box in one motion. In Java terms: you used to check what type an object was and then cast it separately. Pattern matching checks the type AND gives you a ready-to-use variable in one line — no redundant ceremony.

Every Java codebase written before Java 16 has at least one method that looks like a long chain of instanceof checks followed by explicit casts. It works, but it's noisy, repetitive, and a quiet source of bugs — cast the wrong type and you get a ClassCastException at runtime with no compiler warning. The real cost isn't the extra line; it's the cognitive overhead of mentally tracking which type you've already confirmed while reading through a wall of if-else branches.

Pattern matching was introduced to solve exactly this friction. It lets the compiler carry the type knowledge forward so you don't have to. Instead of check → cast → use (three steps), you get check-and-bind (one step). This isn't just syntactic sugar — it's a language-level guarantee: the binding variable is only in scope where the compiler can prove the type holds, which eliminates an entire class of runtime errors.

By the end of this article you'll understand how pattern matching for instanceof works at the bytecode level, how switch pattern matching (Java 21) lets you write exhaustive, compiler-verified dispatch logic, how sealed classes and records compose with patterns to build airtight domain models, and what production gotchas can bite you even when everything compiles cleanly. We'll go deep — this is the stuff that separates developers who know the syntax from those who understand the design.

What is Pattern Matching in Java?

Pattern matching is a language feature that combines type checking with variable binding in a single operation. Instead of writing:

``java if (obj instanceof String) { String s = (String) obj; // use s } ``

``java if (obj instanceof String s) { // use s directly } ``

This is not just shorter — it eliminates a whole class of bugs where the cast fails because the type changed between check and cast. The binding variable is only in scope if the pattern matches, so you can't accidentally use it outside the branch.

The feature evolved through multiple Java versions
  • Java 16: Pattern matching for instanceof (preview in 14, final in 16)
  • Java 17: Sealed classes (final)
  • Java 19: Record patterns (preview)
  • Java 21: Pattern matching for switch (final), record patterns (final)

Each step widens the scope: from simple type checks to complex data structure destructuring and exhaustive dispatch.

io/thecodeforge/matching/ShapeMatcher.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package io.thecodeforge.matching;

// Traditional approach vs pattern matching
public class ShapeMatcher {
    public static void main(String[] args) {
        Object shape = new Circle(5.0);
        
        // Old way
        if (shape instanceof Circle) {
            Circle c = (Circle) shape;
            System.out.println("Area: " + c.area());
        }
        
        // Pattern matching
        if (shape instanceof Circle c) {
            System.out.println("Area: " + c.area());
        }
    }
    
    static class Circle {
        double radius;
        Circle(double r) { radius = r; }
        double area() { return Math.PI * radius * radius; }
    }
}
Key Insight
The binding variable c is only in scope within the if block. This is enforced at compile time, so you cannot accidentally use it after the block or in the else branch. This scoping is a safety guarantee that manual casts lack.
Production Insight
Pattern matching for instanceof compiles to the same bytecode as an instanceof check followed by a checkcast instruction. No performance overhead for simple patterns.
However, in deeply nested if-else chains, the compiler may reorder checks. If you rely on side effects in guards, you are in for a surprise.
Always prefer switch with patterns over long if-else chains for exhaustiveness guarantees.
Key Takeaway
Pattern matching unifies type test and variable binding.
It eliminates an entire class of ClassCastException bugs.
Prefer switch for exhaustiveness; instanceof for simple binary checks.
When to Use Which Pattern Matching Form
IfSingle type check, no further branching
UseUse instanceof pattern in an if statement
IfMultiple types to dispatch, all known at compile time
UseUse switch pattern matching with sealed classes
IfNeed to destructure fields of a record
UseUse record patterns inside switch or instanceof
IfGuarded dispatch (extra condition on the matched value)
UseUse switch with when clause

Instanceof Pattern Matching in Depth

The simplest form of pattern matching is the instanceof pattern, stabilized in Java 16. It allows you to write:

``java if (obj instanceof String s && s.length() > 5) { // s is a String and length > 5 } ``

Note the conjunction: the pattern variable s is available in the subsequent conditions of the same expression due to flow scoping. This works with &&, ||, and negation (!).

Flow scoping means the compiler tracks when a variable is definitely matched. For example: ``java if (!(obj instanceof String s)) { // s is NOT in scope here } // s is NOT in scope here either, because we can't guarantee it matched ``

But if you combine with ||: ``java if (obj instanceof String s || obj instanceof Integer i) { // Here neither s nor i is reliably in scope because either could be true } `` The compiler is conservative — it only allows pattern variables where they are guaranteed to be bound on all paths leading to that code point.

This precision means you can write complex type checks without worrying about variable leaks.

io/thecodeforge/matching/FlowScopingDemo.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package io.thecodeforge.matching;

public class FlowScopingDemo {
    public static void main(String[] args) {
        Object obj = "hello world";
        
        // Works: pattern variable in scope in then-block
        if (obj instanceof String s && s.length() > 3) {
            System.out.println("Long string: " + s);
        }
        
        // Does NOT compile: pattern variable not definitely assigned
        // if (obj instanceof String s || obj instanceof Integer i) {
        //     System.out.println(s);  // compile error
        // }
        
        // Negation with explicit scope
        if (!(obj instanceof String s)) {
            // s not in scope here
            System.out.println("Not a string");
        } else {
            // s is in scope here: compiler knows it matched
            System.out.println("String length: " + s.length());
        }
    }
}
Common Trap with Negation
When using ! with instanceof pattern, remember that the pattern variable is NOT in scope in the true branch (the negation branch). It IS in scope in the else branch. This is counterintuitive for many developers.
Production Insight
Flow scoping is a compile-time safety net, but it can lead to subtle bugs if you rely on side effects in the condition. For example, if you call a mutating method inside a guard, the mutation may affect subsequent parts of the condition after the pattern has matched.
Keep guards pure: no side effects, no state mutation. Treat them as simple predicates.
Rule: if your guard has a side effect, refactor to a separate check.
Key Takeaway
Flow scoping is the superpower of instanceof patterns.
Pattern variables are only in scope where the compiler can prove they matched.
Use guards with caution — keep them side-effect-free.
Instanceof Pattern Usage
IfNeed to check type and use result immediately
UseUse if (obj instanceof Type var)
IfNeed to check type and condition in one expression
UseUse if (obj instanceof Type var && var.isActive())
IfMultiple conditions with OR
UseAvoid pattern variables in that case; extract to local variable first
IfNegation: "if not of type"
UseUse if (!(obj instanceof Type var)) then handle in else

Switch Pattern Matching and Exhaustiveness

Java 21 brought pattern matching to switch statements and expressions. This is where the real power lies: you can now match on multiple patterns, including guards (using when), and the compiler will check that all cases are covered.

Basic switch pattern matching: ``java String formatted = switch (shape) { case Circle c -> "Circle radius: " + c.radius(); case Rectangle r -> "Rectangle area: " + r.area(); case Square s -> "Square side: " + s.side(); default -> "Unknown shape"; }; ``

But the real game-changer is exhaustiveness with sealed classes. If your hierarchy is sealed, you can omit the default clause: ```java sealed interface Shape permits Circle, Rectangle, Square { }

String formatted = switch (shape) { case Circle c -> "Circle radius: " + c.radius(); case Rectangle r -> "Rectangle area: " + r.area(); case Square s -> "Square side: " + s.side(); // No default needed — compiler knows all cases covered }; ``` If you later add a new subtype to the permits clause, the switch will fail to compile until you add the missing case. This is a powerful safety net.

Guards allow you to add extra conditions: ``java case Shape s when s.area() > 100 -> "Large shape: " + s; `` The guard is evaluated after the pattern matches. If the guard fails, the next case is tried.

Total patterns like case Object o act as a catch-all when you cannot use sealed classes. But they disable exhaustiveness checking — use sparingly.

io/thecodeforge/matching/SwitchPatternDemo.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package io.thecodeforge.matching;

public class SwitchPatternDemo {
    sealed interface Shape permits Circle, Rectangle, Square { }
    record Circle(double radius) implements Shape {
        double area() { return Math.PI * radius * radius; }
    }
    record Rectangle(double width, double height) implements Shape {\n        double area() { return width * height; }
    }
    record Square(double side) implements Shape {
        double area() { return side * side; }
    }
    
    public static void main(String[] args) {
        Shape shape = new Circle(5.0);
        String description = switch (shape) {
            case Circle c && c.radius() > 3 -> "Big circle";
            case Circle c -> "Small circle";
            case Rectangle r -> "Rectangle";
            case Square s -> "Square";
            // No default needed
        };
        System.out.println(description);
    }
}
Think of sealed + switch as a closed world
  • Without sealed: switch needs default or total pattern — open world
  • With sealed: compiler enforces all branches — no default needed
  • Adding a new subtype requires updating all switches — but that's a feature, not a bug
  • Guard conditions narrow the match but still within the sealed set
Production Insight
Exhaustiveness is a compile-time guarantee only when the hierarchy is sealed. If you use an interface or abstract class without sealed, the compiler cannot know all subtypes. In that case, you must provide a default branch.
A common production mistake is using a sealed hierarchy but then forgetting to update all switches when a new subtype is added. The code won't compile — that's the safety. But if you have multiple modules, you may need to adjust build dependencies.
Always run a full build after adding a new subtype to a sealed hierarchy.
Benchmark point: switch pattern matching over sealed hierarchy compiles to a tableswitch or lookupswitch bytecode, same as traditional switch on enum. No boxing overhead.
Key Takeaway
Switch pattern matching with sealed classes = compile-time exhaustive dispatch.
Without sealed, you need an explicit default branch.
Guards are evaluated after pattern match — they must be side-effect-free.
Switch Pattern Matching Decisions
IfExhaustive dispatch over known subtypes
UseUse sealed hierarchy with switch expression (no default)
IfNeed extra condition after pattern match
UseAdd a when guard clause
IfUnknown or extensible hierarchy
UseUse switch with case Object o total pattern or explicit default
IfSwitch returns a value
UseUse switch expression (-> or yield). Be mindful of exhaustiveness

Record Patterns and Destructuring

Record patterns (final in Java 21) allow you to destructure a record into its components directly in a pattern match. This is especially powerful when combined with switch:

```java record Point(int x, int y) { } record Line(Point start, Point end) { }

String describe(Object obj) { return switch (obj) { case Point(int x, int y) -> "Point at (" + x + ", " + y + ")"; case Line(Point(var x1, var y1), Point(var x2, var y2)) -> "Line from (" + x1 + "," + y1 + ") to (" + x2 + "," + y2 + ")"; default -> "Unknown"; }; } ```

Note the use of var in nested patterns — you can omit the type when it's clear from the record component type. You can also use guards on the destructured values: ``java case Point(int x, int y) when x == y -> "Diagonal point"; ``

Record patterns work with both switch and instanceof: ``java if (obj instanceof Point(int x, int y)) { System.out.println("Point at " + x + ", " + y); } ``

This is a massive reduction in boilerplate for data-centric code — no more manual getter calls or if-null checks.

io/thecodeforge/matching/RecordPatternDemo.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package io.thecodeforge.matching;

public class RecordPatternDemo {
    record Point(int x, int y) { }
    record Circle(Point center, double radius) { }
    
    public static void main(String[] args) {
        Object obj = new Circle(new Point(3, 4), 5.0);
        String desc = switch (obj) {
            case Circle(Point(var x, var y), var r) when r > 10.0 -> 
                "Large circle at (" + x + "," + y + ")";
            case Circle(Point(var x, var y), var r) -> 
                "Circle at (" + x + "," + y + ") radius " + r;
            case Point p -> "Point at (" + p.x() + "," + p.y() + ")";
            default -> "Unknown";
        };
        System.out.println(desc);
    }
}
Use 'var' in nested patterns for clarity
When destructuring deeply nested records, use var for component types to keep the pattern readable. The compiler infers the type from the record component declaration. Only specify the type explicitly if you want to narrow it (e.g., if the component is declared as Object).
Production Insight
Record patterns compile to a series of getter calls and instanceof checks. If the record has a large number of components, the bytecode grows linearly but the runtime cost is still minimal. However, nested record patterns can lead to exponential blowup in bytecode size if you have many levels — not a performance issue but a class file size concern.
Another gotcha: record patterns only work with records (or deconstruction patterns for classes, which are still preview in JDK 23). You cannot destructure a regular POJO.
Debugging tip: use -XX:+ShowPattern VM flag to see how the JVM compiles record patterns.
Key Takeaway
Record patterns destructure data directly in the pattern.
Nested patterns with var keep code clean.
They are a compile-time expansion into getter calls — no runtime overhead.
When to Use Record Patterns
IfYou have a record and need to extract fields directly in a match
UseUse record pattern with switch or instanceof
IfYou have regular classes (non-record)
UseCannot use record patterns. Consider converting to records or manual destructuring
IfNeed to match on field values with conditions
UseUse record pattern with when guard
IfNested records (e.g., Order(Customer, List<Item>))
UseNested record patterns work, but be mindful of readability

Production Gotchas and Performance Considerations

Pattern matching in Java is designed to be efficient, but there are nuances:

  1. Pattern ordering matters: In switch, patterns are tried in order. If you have overlapping patterns (e.g., Number n before Integer i), the more specific pattern will never match. The compiler warns about unreachable code in simple cases but not in all.
  2. Guard evaluation order: The guard is evaluated after the pattern matches. If the guard throws an exception, it propagates as if it were any other runtime exception. Guards that depend on mutable state can cause nondeterministic behaviour.
  3. Null handling: In switch expressions, null does not match any pattern unless you have an explicit case null. Before Java 21, switch threw NullPointerException. Now you can handle null:
  4. ```java
  5. switch (obj) {
  6. case null -> "null";
  7. case String s -> s;
  8. // ...
  9. }
  10. ```
  11. Performance: For switch over sealed classes with a small number of subtypes (≤ 10), the JVM uses tableswitch which is O(1). For larger sets or non-sealed hierarchies, it may fall back to lookupswitch or a series of instanceof checks. In practice, the performance difference is negligible for most code.
  12. Bytecode size: Record patterns and nested patterns can lead to significantly larger bytecode because each component becomes a getter call. This is rarely a problem but can affect startup time on class-loading-heavy applications.
  13. Backwards compatibility: Pattern matching on existing legacy code may introduce subtle changes if you refactor a long if-else chain. Always test thoroughly.
io/thecodeforge/matching/NullHandlingDemo.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package io.thecodeforge.matching;

public class NullHandlingDemo {
    public static void main(String[] args) {
        Object obj = null;
        String result = switch (obj) {
            case null -> "Received null";
            case String s -> "String: " + s;
            case Integer i -> "Integer: " + i;
            default -> "Unknown";
        };
        System.out.println(result);
    }
}
Null and switch: you must handle null explicitly
Before Java 21, a null operand to switch caused NullPointerException. With pattern matching in switch (Java 21+), you can add a case null branch. If you don't, it still throws NPE. Always add a null case when switching over nullable objects.
Production Insight
Pattern matching can hide performance issues if used carelessly in hot loops. For example, a record pattern that destructures a large record (20+ fields) generates bytecode that calls each getter individually. If this is in a tight loop, it may cause method inlining failures or increased code size.
Measure before optimizing. Use JMH to test your specific scenario. In most CRUD applications, the cost is dominated by I/O, not pattern matching.
Rule: if you are pattern matching in a loop that runs millions of times per second, consider manual optimization. But for 99% of code, it's fine.
Key Takeaway
Pattern matching is performant for sealed hierarchies of reasonable size.
Measure before optimizing — CPU cost is rarely the bottleneck.
Null and pattern order are the most common production gotchas.
Performance Decision Guide
IfHot loop with complex record patterns
UseProfile with JMH. If needed, manually extract fields outside the loop
IfSmall number of subtypes (≤ 10), sealed
UseSwitch pattern matching is as fast as traditional switch
IfLarge number of subtypes (100+)
UsePattern matching may degrade to if-else chain. Consider using a Map-based dispatch
IfSwitch on nullable object
UseAlways include case null to avoid NPE
● Production incidentPOST-MORTEMseverity: high

Midnight MatchException in a Payment Dispatcher

Symptom
At 3:00 AM, user payments started failing with java.lang.MatchException: no case matched: class io.thecodeforge.payment.CryptoPayment. The exception was uncaught because developers assumed the switch was exhaustive.
Assumption
The team assumed that because the class hierarchy was designed for patterns, the compiler would catch missing branches. They had added a new CryptoPayment subclass but forgot to add it to the permits clause of the sealed interface.
Root cause
The sealed interface Payment had permits listing only CreditCardPayment and PayPalPayment. The new CryptoPayment extended Payment without being added to permits, so the compiler didn't know about it. The switch in the dispatcher was exhaustive based on the known subtypes, but at runtime the JVM encountered an unknown subtype and had no matching case.
Fix
1. Add CryptoPayment to the permits clause of the sealed interface. 2. Move the dispatcher switch to use a library-level helper that logs a warning on unknown subtypes to prevent silent failure. 3. Add a unit test that calls the dispatcher with every known subtype to catch exhaustiveness breakage early.
Key lesson
  • Sealed classes enforce exhaustiveness only for compiler-known subtypes. If you add a subtype outside the permits list, the switch still compiles but silently breaks at runtime.
  • Always keep the permits list in sync with actual subtypes. Use compiler annotations or architectural tests to verify.
  • Consider using library-level exhaustiveness checks with a default case that logs and rethrows for truly unknown subtypes.
Production debug guideCommon symptoms and their root causes when pattern matching breaks in production4 entries
Symptom · 01
MatchException: no case matched
Fix
Check the sealed hierarchy permits — ensure all runtime subtypes are included. Inspect the classpath for unexpected jars. Add a default -> throw new RuntimeException(...) in dev to catch missing cases early.
Symptom · 02
ClassCastException from a guard expression
Fix
Guards are evaluated after pattern matching. If the guard calls a method on the pattern variable that does not exist on the matched type, a ClassCastException may occur. Ensure the guard method is defined on the pattern type's interface.
Symptom · 03
Switch expression compiles but returns unexpected result
Fix
Check that all pattern labels are disjoint. Overlapping patterns (e.g., both String s and CharSequence c where String implements CharSequence) result in the first match. Reorder or use guards to disambiguate.
Symptom · 04
Pattern variable not in scope in switch expression
Fix
Remember that in switch expressions, pattern variables are in scope only within the case arm, not in the expression itself. If you need to use the variable in the expression, assign to a local variable inside the arm.
★ Quick Debug Cheat Sheet for Pattern MatchingWhen pattern matching behaves unexpectedly, use these command patterns to investigate
ClassCastException at pattern match
Immediate action
Check the binding variable type matches the actual runtime type. If using switch with patterns, ensure the order of cases is correct (most specific first).
Commands
javap -c -p io.thecodeforge.payment.PaymentDispatcher
javap -verbose io.thecodeforge.payment.Payment | grep -i patterns
Fix now
Add an explicit type check inside the guard to filter out unexpected types.
MatchException when adding new subtype+
Immediate action
Stop the deployment that added the new subtype. Rollback to previous version.
Commands
jar tf payment-service-1.0.jar | grep Payment
grep -r 'permits' src/main/java/io/thecodeforge/payment/
Fix now
Add the new subclass to the permits list and recompile all dependent modules.
Switch expression doesn't cover all cases but compiles+
Immediate action
Look for a default case or a total pattern (like `Object o`) that is swallowing unexpected types.
Commands
grep -A5 'default ->' src/main/java/io/thecodeforge/
find . -name '*.java' -exec grep -l 'permits' {} \;
Fix now
Remove the default case and let the compiler force you to handle all subtypes. If you must have default, add a guard to log and rethrow.
Pattern Matching Forms Compared
FeatureJava VersionPurposeProduction Use Case
Instanceof pattern16 (final)Type check and bind variable in one stepReplacing every manual type-check + cast pattern
Switch with patterns21 (final)Exhaustive dispatch over sealed types with guardsReplacing long if-else chains in business logic
Record patterns21 (final)Destructure records directly in pattern matchData processing with domain models (orders, users)
Sealed classes + permits17 (final)Restrict type hierarchy to known subtypesDomain modeling with compile-time exhaustiveness
Guard (when) clauses21 (final)Add extra condition after pattern matchRefining pattern matches with additional state checks
Null handling in switch21 (final)Explicit null branch in switch expressionsSafe dispatching on nullable values

Key takeaways

1
Pattern matching combines type check and variable binding, eliminating a class of ClassCastException bugs.
2
Flow scoping guarantees pattern variables are only in scope where the compiler can prove they matched.
3
Sealed classes enable compile-time exhaustive switch
no default branch needed.
4
Record patterns destructure data directly in the match, reducing boilerplate.
5
Guards (when) add conditions after pattern match; keep them pure and side-effect-free.
6
Pattern matching compiles to efficient bytecode; measure before optimizing.
7
Always handle null explicitly in switch expressions with pattern matching.

Common mistakes to avoid

5 patterns
×

Using pattern variables outside their scope

Symptom
Compilation error: 'cannot find symbol' on pattern variable. Or using it in a branch where the compiler didn't guarantee it matched.
Fix
Understand flow scoping. Pattern variables are only in scope where the compiler can prove the pattern matched. Use separate if-else or switch to ensure proper scoping.
×

Overlapping patterns in switch

Symptom
A more specific pattern never matches because a broader pattern comes first (e.g., Number n before Integer i).
Fix
Order patterns from most specific to least specific. The compiler warns about unreachable code in simple cases, but not always. Test with representative data.
×

Forgetting to handle null in switch expressions

Symptom
NullPointerException when the operand is null. Before Java 21, this was expected; after Java 21, you can add case null.
Fix
Always add a case null -> branch if the operand can be null. If null is unexpected, use case null -> throw new IllegalArgumentException("Unexpected null").
×

Not updating permits when adding a new subtype

Symptom
Compiler may not catch missing switch branches if the new subtype is not in the permits list. At runtime, MatchException thrown.
Fix
Keep the permits clause in sync with all concrete subtypes. Use a build-time check to verify all subtypes are listed.
×

Expecting record patterns to work on regular classes

Symptom
Compilation error: record patterns only accept record types. Cannot destructure a POJO.
Fix
Convert the class to a record if possible. Otherwise, use manual destructuring or a deconstruction pattern (preview in JDK 23).
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain flow scoping in the context of instanceof pattern matching. Can ...
Q02SENIOR
What is the purpose of sealed classes in pattern matching? How do they a...
Q03SENIOR
How does the JVM compile a switch expression with record patterns? What ...
Q04SENIOR
What happens if a guard clause throws an exception? Does the switch try ...
Q05JUNIOR
Can pattern matching be used with primitives? Explain.
Q01 of 05SENIOR

Explain flow scoping in the context of instanceof pattern matching. Can you use a pattern variable in the else branch?

ANSWER
Flow scoping means the compiler determines where a pattern variable is definitely bound. For if (obj instanceof String s), the variable s is in scope inside the true branch but not in the false branch. However, if you use ! negation: if (!(obj instanceof String s)), then s is NOT in scope in the true branch (the negation branch), but IS in scope in the else branch. This is because the else branch means the pattern matched. The compiler is conservative: it only allows variables where all paths guarantee the match.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
What is the difference between pattern matching for instanceof and traditional instanceof + cast?
02
Can I use pattern matching with enums?
03
Are record patterns available in Java 21?
04
Does pattern matching work with anonymous classes?
05
What is the impact on serialization when using sealed classes with pattern matching?
06
Can I use pattern matching with arrays?
🔥

That's Java 8+ Features. Mark it forged?

6 min read · try the examples if you haven't

Previous
Sealed Classes in Java 17
13 / 16 · Java 8+ Features
Next
CompletableFuture vs Future