Sitemap

Matching Pattern for java objects

4 min readMay 14, 2025

--

Press enter or click to view image in full size

Extracting data from objects enables us to write concise, safe, and maintainable code, making it easier for other developers to understand and modify. Java has introduced pattern matching to simplify common coding tasks, particularly when working with object types and control structures. The two main features are:

  1. instanceof + Pattern Matching (Java 16+)
  2. switch Pattern Matching (Java 17 preview, finalized in Java 21)

1. Pattern Matching with instanceof

Before Java 16, type-checking and casting were separate steps:

if (obj instanceof String) {
String s = (String) obj; // Manual casting
System.out.println(s.length());
}

With pattern matching (Java 16+):

if (obj instanceof String s) { // Auto-casting into 's'
System.out.println(s.length()); // No explicit cast needed
}

2. Pattern Matching in switch (Java 21+)

Java 21 finalized pattern matching for switch, allowing type-based branching without instanceof chains:

if (shape instanceof Circle) {
Circle c = (Circle) shape;
System.out.println("Radius: " + c.getRadius());
} else if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape;
System.out.println("Area: " + r.getWidth() * r.getHeight());
} else {
System.out.println("Unknown shape");
}

Modern Approach (Java 21+)

switch (shape) {
case Circle c -> System.out.println("Radius: " + c.getRadius());
case Rectangle r -> System.out.println("Area: " + r.getWidth() * r.getHeight());
default -> System.out.println("Unknown shape");
}

3. Advanced Use Cases

3.1 Matching on null (Java 21+)

switch (obj) {
case String s -> System.out.println("String: " + s);
case null -> System.out.println("Null object!"); // Explicit null handling
default -> System.out.println("Other type");
}

3.2 Nested Patterns (Java 21+)

if (obj instanceof Point(int x, int y)) { // Deconstructing a record
System.out.println("X: " + x + ", Y: " + y);
}

3.3 Sealed Classes + Pattern Matching

sealed interface Shape permits Circle, Rectangle { }

record Circle(double radius) implements Shape { }
record Rectangle(double w, double h) implements Shape { }

// Exhaustive pattern matching (compiler checks all cases)
double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.w() * r.h();
};
}

A sealed interface is an interface that restricts which classes or interfaces can implement it. This provides fine-grained control over inheritance hierarchies, improving API design and enabling safer pattern matching.

4. Generic types as operands

The instanceof operator in Java can be used with generic types, but there are strict rules about which combinations are valid. These rules depend on the subtype relationship between the generic types and how they are parameterized.

Get Tadeo Aragón’s stories in your inbox

Join Medium for free to get updates from this writer.

4.1 Direct Relationship (ArrayList<String> → List<String>)

List<String> stringList = new ArrayList<>();
if (stringList instanceof ArrayList<String>) { // Valid
System.out.println("ArrayList<String> detected");
}

Why it works:

  • ArrayList<String> is a direct subtype of List<String>
  • Both the collection type (ArrayList) and generic parameter (String) match exactly

4.2 No Type Relationship — Invariance (Invalid)

List<String> stringList = new ArrayList<>();
if (stringList instanceof List<CharSequence>) { // Compile error
System.out.println("This will never work");
}

Why it fails:

  • Although String extends CharSequence
  • Generics are invariant: List<String> is NOT a subtype of List<CharSequence>
  • Prevents unsafe additions of other CharSequence types

The following code demonstrates why Java’s generic type invariance is necessary, showing what could go wrong if List<String> were incorrectly treated as a subtype of List<CharSequence>

// Hypothetical dangerous scenario (doesn't actually compile)
List<String> stringList = new ArrayList<>();
stringList.add("Hello");

// If Java allowed this (which it doesn't):
List<CharSequence> charSeqList = stringList; // ACTUALLY PROHIBITED BY COMPILER

// Then we could corrupt the original list:
charSeqList.add(new StringBuilder()); // Adding non-String to List<String>
String s = stringList.get(1); // ClassCastException at runtime!

// This is why Java generics are invariant

4.3 Unbounded Wildcard (Valid)

List<String> stringList = new ArrayList<>();
if (stringList instanceof List<?>) { // Valid
System.out.println("Matches any List type");
}

Why it works:

  • List<?> is supertype of all List<T> variants
  • Safe because it makes no assumptions about element type
  • Can only read as Object, can't add elements

4.4 Bounded Wildcard (Valid with restrictions)

List<String> stringList = new ArrayList<>();
if (stringList instanceof List<? extends CharSequence>) { // Valid
System.out.println("Matches CharSequence subtypes");
CharSequence cs = stringList.get(0); // Can read
// stringList.add("new"); // Can't add (compile error)
}

Why it works:

  • ? extends CharSequence includes String and other subtypes
  • Safe for reading but not writing

Use extends to produce (get) and super to consume (add).

4.4 Raw Type (Valid but dangerous)

List<String> stringList = new ArrayList<>();
if (stringList instanceof List) { // Valid but warning
System.out.println("Raw type match");
// Loses all generic type safety
}

Why it works but is bad:

  • Backward compatibility with pre-Java 5 code
  • Bypasses generic type checking
  • Generates compiler warnings

Conclusion

Pattern matching with instanceof and switch represents a natural evolution in Java, designed to:

  1. Reduce Boilerplate Code
  • Eliminates verbose type checks and manual casting.
  • Combines type testing and variable binding in a single step.

2. Enhance Code Safety & Readability

  • Compile-time type verification prevents ClassCastException.
  • Clear, expressive syntax aligns with modern coding practices.

3. Future-Proof Your Designs

  • Seamlessly integrates with newer features like sealed classes and records
  • Prepares codebases for upcoming Java enhancements

--

--