Pattern Matching for switch

Java 17 enhances pattern matching for switch statements and expressions, building on features introduced in earlier versions. It introduces sealed classes and pattern matching to make switch constructs more expressive and concise. Below is a detailed explanation of pattern matching in switch as of Java 17, including examples.

Important Features of Pattern Matching for switch in Java 17

  • Type Patterns:

  • Allows switch to match based on the type of an object and automatically cast it to that type.
  • Eliminates the need for explicit instanceof checks and casting.
  • Introduced in Java 16 (JEP 394) and stabilized in Java 17.
  • Sealed Classes:

  • Java 17 finalizes sealed classes (JEP 409), which restrict which classes can extend or implement a type.
  • When used with pattern matching in switch, the compiler can ensure exhaustiveness (no need for a default branch if all permitted subclasses are covered).
  • Switch Expressions:

  • Java 17 supports switch as an expression (returning a value) in addition to traditional switch statements.
  • Uses -> (arrow syntax) for concise case handling and yield for returning values in blocks.
  • Guarded Patterns (Preview in Java 17):

  • Introduced as a preview feature (JEP 406), guarded patterns allow additional conditions in switch cases using &&.
  • Enhances expressiveness by combining type checks with predicates.
  • Null Handling:

  • Java 17 allows switch to handle null cases explicitly, reducing the risk of NullPointerException.
switch (variable) {
    case Type1 varName -> expression;
    case Type2 varName && condition -> { block; yield value; }
    case null -> expression;
    default -> expression;
}Code language: JavaScript (javascript)

Examples

1. Basic Type Pattern Matching

Object obj = "Hello, Java 17!";
String result = switch (obj) {
    case String s -> "String: " + s;
    case Integer i -> "Integer: " + i;
    case null -> "Null value";
    default -> "Other type";
};
System.out.println(result); // Output: String: Hello, Java 17!Code language: JavaScript (javascript)
  • The switch checks the type of obj, casts it to the matched type (e.g., String s), and executes the corresponding branch.
  • No explicit instanceof or casting is needed.

2. Sealed Classes with Exhaustive switch

sealed interface Shape permits Circle, Rectangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}

Shape shape = new Circle(5.0);
double area = switch (shape) {
    case Circle c -> Math.PI * c.radius() * c.radius();
    case Rectangle r -> r.width() * r.height();
};
System.out.println(area); // Output: 78.53981633974483Code language: PHP (php)
  • Since Shape is sealed and only permits Circle and Rectangle, the switch is exhaustive without a default branch.
  • The compiler ensures all permitted subclasses are covered.

3. Guarded Patterns (Preview Feature)

To use guarded patterns, you must enable preview features (e.g., compile with –enable-preview –source 17).

Object obj = "Java";
String result = switch (obj) {
    case String s && s.length() > 3 -> "Long string: " + s;
    case String s -> "Short string: " + s;
    case null -> "Null";
    default -> "Not a string";
};
System.out.println(result); // Output: Long string: JavaCode language: JavaScript (javascript)
  • The && clause adds a condition to the type pattern, allowing finer-grained control.

4. Handling null Explicitly

String str = null;
String result = switch (str) {
    case null -> "Null string";
    case String s -> "Non-null string: " + s;
};
System.out.println(result); // Output: Null stringCode language: JavaScript (javascript)
  • Explicit null case prevents NullPointerException.

5. Traditional switch Statement with Pattern Matching

Object obj = 42;
switch (obj) {
    case String s -> System.out.println("String: " + s);
    case Integer i -> System.out.println("Integer: " + i);
    case null -> System.out.println("Null");
    default -> System.out.println("Other");
}
// Output: Integer: 42Code language: JavaScript (javascript)

Benefits

  • Conciseness: Reduces boilerplate code (no instanceof or casting).
  • Safety: Exhaustiveness checks with sealed classes prevent missing cases.
  • Flexibility: Guarded patterns and null handling make switch more powerful.
  • Readability: Arrow syntax and type patterns improve code clarity.

Limitations

  • Preview Features: Guarded patterns are a preview feature in Java 17, requiring –enable-preview.
  • Scope: Pattern matching in switch is limited to type patterns and guarded patterns; more advanced patterns (e.g., deconstruction) were introduced in later Java versions (e.g., Java 21).
  • Sealed Classes Requirement: Exhaustiveness checks require sealed types for complex hierarchies.

Use case and Implementation

This use case processes tasks assigned to software engineers based on their designation (e.g., Junior, Mid-Level, Senior, Tech Lead). Each designation has specific responsibilities, such as coding, designing, mentoring, or managing projects.

// Enum for Software Engineer designations
enum Designation {
    JUNIOR, MID_LEVEL, SENIOR, TECH_LEAD
}

// Main class to demonstrate ProcessEngineerTask use case
public class Switch17 {
    // Use Case: ProcessEngineerTask
    // Processes tasks based on the engineer's designation
    static String processEngineerTask(Designation engineer, String task) {
        return switch (engineer) {
            case JUNIOR -> "Junior Engineer is working on: " + task + " (Coding)";
            case MID_LEVEL -> "Mid-Level Engineer is working on: " + task + " (Design/Coding)";
            case SENIOR -> "Senior Engineer is working on: " + task + " (Mentoring/Design)";
            case TECH_LEAD -> "Tech Lead is working on: " + task + " (Managing)";
        };
    }

    public static void main(String[] args) {
        // Sample engineers and tasks
        Designation[] engineers = {
                Designation.JUNIOR,
                Designation.MID_LEVEL,
                Designation.SENIOR,
                Designation.TECH_LEAD

        };
        String[] tasks = {
                "Fix bug",
                "Design API",
                "Review code",
                "Plan sprint",
                "Handle error"
        };

        // Process tasks (Use Case: ProcessEngineerTask)
        System.out.println("Processing tasks (Use Case: ProcessEngineerTask)");
        System.out.println("---------------------------------------------");
        for (int i = 0; i < engineers.length; i++) {
            String result = processEngineerTask(engineers[i], tasks[i]);
            System.out.println(result);
        }
    }
}

/*
Junior Engineer is working on: Fix bug (Coding)
Mid-Level Engineer is working on: Design API (Design/Coding)
Senior Engineer is working on: Review code (Mentoring/Design)
Tech Lead is working on: Plan sprint (Managing)
 */Code language: JavaScript (javascript)
Pattern matching for switch in Java 17 significantly enhances the language’s expressiveness and conciseness, enabling developers to write cleaner, type-safe code. By supporting type patterns, null handling, and switch expressions, Java 17 allows seamless handling of complex logic, such as processing software engineer designations in the ProcessEngineerTask use case. Combined with sealed classes for exhaustiveness and preview features like guarded patterns, it reduces boilerplate, improves readability, and minimizes errors like NullPointerException. As a Long-Term Support release, Java 17 solidifies pattern matching in switch as a robust, future-proof feature for building maintainable and scalable applications.
Scroll to Top