Master Core Java Programming From Scratch

Clear, interactive, and structured coding lessons designed for absolute beginners.

Module 9

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.

Key idea: Pattern matching can make type-based code shorter, safer, and easier to read.

2. Traditional instanceof

Before Java 16, developers commonly checked the type and then explicitly cast the object.

Java
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.

Java
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.

Java
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.

Java
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

Java
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.

Java
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";
    };
}
Benefit: The explicit casts that were common in older type-based branching code are no longer necessary.

9. Pattern Matching for switch in Java 21

Pattern matching for switch became a permanent standard language feature in Java 21.

Java
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.

Java
static String describe(Object value) {

    return switch (value) {

        case null ->
            "Value is null";

        case String text ->
            "String: " + text;

        default ->
            "Other value";
    };
}
Important: A null selector should be handled explicitly when null is a valid input for the application.

11. Pattern Matching with when

Modern Java supports additional conditions on patterns using the when guard syntax.

Java
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";
    };
}
Version note: The 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.

Java
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.

Java
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.

Java
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.

Java
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";
    };
}
Modern Java: Record patterns became a permanent feature in Java 21.

15. Nested Record Patterns

Record patterns can be nested when records contain other records.

Java
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.

Java
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 🤓🤷‍♀️

Java
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

Pattern matching combines type checking and variable binding into a concise syntax, reducing explicit casting.

Pattern matching for instanceof became a standard feature in Java 16.

Pattern matching for switch became a standard feature in Java 21.

A pattern variable is a variable introduced by a successful type pattern, such as value instanceof String text.

Record patterns allow Java code to match a record and extract its component values directly.

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