Pattern Matching in Java
Learn modern Java pattern matching with instanceof and switch.
1. What is Pattern Matching?
Pattern matching is a modern Java language feature that combines type checking, type conversion, and conditional logic into a more concise syntax.
Before pattern matching, Java code often required an explicit
instanceof check followed by a cast.
2. Traditional instanceof
Before Java 16, developers commonly checked the type and then explicitly cast the object.
Object value = "Hello world CIIT 🌍🤓";
if (value instanceof String) {
String text = (String) value;
System.out.println(text.length());
}
3. Pattern Matching with instanceof
Modern Java allows the type test and variable declaration to be combined.
Object value = "Hello world CIIT 🌍🤓";
if (value instanceof String text) {
System.out.println(text.length());
}
Here, text is the pattern variable. If the object is a
String, Java automatically makes the variable available
in the valid scope.
4. Scope of Pattern Variables
A pattern variable is available where the compiler can prove that the pattern has matched.
Object value = "CIIT";
if (value instanceof String text
&& text.length() > 3) {
System.out.println(text);
}
The variable text is available on the right side of
&& because the first condition establishes that
it is a String.
5. Pattern Matching with Negation
Pattern variables can also work with negated conditions.
Object value = "CIIT";
if (!(value instanceof String text)) {
System.out.println("Validation failed: Data is not a valid text record inside the CIIT system 🫥.");
return;
}
System.out.println(
"String length: " + text.length()
);
After the early return, the compiler knows that text
represents a String.
6. Pattern Matching with Multiple Conditions
Object value = "Programming";
if (value instanceof String text
&& !text.isEmpty()
&& text.length() > 5) {
System.out.println(
"Long text: " + text
);
}
7. Pattern Matching for instanceof
Pattern matching for instanceof became a standard feature
in Java 16.
| Old Style | Modern Style |
|---|---|
| Check type separately | Check type and declare variable together |
| Requires explicit cast | Automatic pattern variable |
| More verbose | More concise |
8. Pattern Matching with switch
Modern Java extends pattern matching to switch expressions. This allows switch branches to match objects based on their types.
static String describe(Object value) {
return switch (value) {
case String text ->
"String: " + text;
case Integer number ->
"Integer: " + number;
case Double number ->
"Double: " + number;
default ->
"Unknown type";
};
}
9. Pattern Matching for switch in Java 21
Pattern matching for switch became a permanent standard language feature in Java 21.
static String getType(Object value) {
return switch (value) {
case String text ->
"Text";
case Integer number ->
"Integer";
case Long number ->
"Long";
default ->
"Other";
};
}
10. Handling null in Pattern Matching
A modern switch can explicitly handle null.
static String describe(Object value) {
return switch (value) {
case null ->
"Value is null";
case String text ->
"String: " + text;
default ->
"Other value";
};
}
11. Pattern Matching with when
Modern Java supports additional conditions on patterns using
the when guard syntax.
static String classify(Object value) {
return switch (value) {
case Integer number when number > 0 ->
"Positive number";
case Integer number ->
"Zero or negative number";
case String text when !text.isEmpty() ->
"Non-empty text";
case String text ->
"Empty text";
default ->
"Other";
};
}
when guard syntax belongs to the modern
pattern-matching switch language features available in recent Java versions.
12. Pattern Matching with Sealed Classes
Pattern matching works especially well with sealed class hierarchies, because the compiler can reason about the permitted subclasses.
sealed interface Shape
permits Circle, Rectangle {
}
final class Circle implements Shape {
double radius;
}
final class Rectangle implements Shape {
double width;
double height;
}
A switch can then handle the permitted types.
static String describe(Shape shape) {
return switch (shape) {
case Circle circle ->
"Circle";
case Rectangle rectangle ->
"Rectangle";
};
}
13. Exhaustive Pattern Matching
Pattern matching switch expressions must be exhaustive. Every possible input must be covered.
With a sealed hierarchy, the compiler can use the permitted subclasses to determine whether all cases have been handled.
sealed interface Payment
permits CardPayment, CashPayment {
}
final class CardPayment implements Payment {
}
final class CashPayment implements Payment {
}
static String process(Payment payment) {
return switch (payment) {
case CardPayment card ->
"Processing card";
case CashPayment cash ->
"Processing cash";
};
}
14. Pattern Matching with Records
Modern Java also supports record patterns, allowing components of a record to be matched and extracted directly.
record Person(String name, int age) {
}
static String describe(Object value) {
return switch (value) {
case Person(String name, int age) ->
name + " is " + age + " years old";
default ->
"Unknown";
};
}
15. Nested Record Patterns
Record patterns can be nested when records contain other records.
record Address(String city) {
}
record Employee(String name, Address address) {
}
static String getCity(Employee employee) {
return switch (employee) {
case Employee(
String name,
Address(String city)
) -> city;
};
}
16. Working with Object Values
Pattern matching is useful when a method receives a broad type such as
Object.
static void printValue(Object value) {
if (value instanceof String text) {
System.out.println(
"Text: " + text
);
} else if (value instanceof Integer number) {
System.out.println(
"Number: " + number
);
} else {
System.out.println(
"Other value"
);
}
}
17. instanceof vs Pattern Matching switch
| instanceof Pattern | switch Pattern |
|---|---|
| Useful for a small number of type checks | Useful for multiple alternatives |
| Works naturally inside if statements | Works naturally as a switch expression |
| Good for conditional logic | Good for exhaustive branching |
18. Advantages of Pattern Matching
- Reduces explicit casting.
- Improves readability.
- Provides compile-time type safety.
- Works with
instanceof. - Works with switch expressions.
- Works with sealed class hierarchies.
- Works with record patterns.
- Can simplify type-based business logic.
19. Things to Remember
- Pattern variables have a defined scope.
- Switch expressions must be exhaustive.
- Use a suitable Java version for the syntax being used.
- Do not make patterns unnecessarily complex.
- Prefer simple and readable branching logic.
- Use sealed hierarchies when exhaustive type handling is valuable.
20. Example 🤓🤷♀️
public class Main {
static String describe(Object value) {
return switch (value) {
case null ->
"Null value";
case String text when !text.isEmpty() ->
"Text: " + text;
case Integer number when number > 0 ->
"Positive integer: " + number;
case Integer number ->
"Non-positive integer: " + number;
default ->
"Other value";
};
}
public static void main(String[] args) {
System.out.println(
describe("Java")
);
System.out.println(
describe(100)
);
System.out.println(
describe(-5)
);
System.out.println(
describe(null)
);
}
}
21. Best Practices
- Use pattern matching when it makes type checks simpler.
- Keep pattern conditions easy to understand.
- Use switch expressions for multiple type alternatives.
- Use sealed classes when a closed type hierarchy is appropriate.
- Use record patterns when extracting record components improves clarity.
- Handle null deliberately.
- Compile the project using the Java version required by the syntax.
22. Interview Questions
instanceof became a
standard feature in Java 16.
value instanceof String text.
Summary
In this lesson, you learned:
- Traditional instanceof checks
- Pattern matching with instanceof
- Pattern variable scope
- Pattern matching with switch
- Switch pattern matching in Java 21
- Handling null
- Pattern guards using when
- Sealed class pattern matching
- Record patterns
- Nested record patterns
- Advantages and best practices