DEV Community

9mac-dev
9mac-dev

Posted on

Java 17 Features - Part 4: Pattern Matching & Switch Expressions

This is a teaser article. Pattern matching represents Java's first serious steps toward a programming paradigm that functional languages have enjoyed for decades. Combined with switch expressions, these features fundamentally transform how you write conditional logic and type checks. This article explores pattern matching for instanceof, switch expressions, guarded patterns, record destructuring, and how sealed types guarantee exhaustiveness.


Why This Matters

For decades, Java developers have suffered through the "instanceof-cast ceremony":

// The old pain point - Java 8
if (obj instanceof String) {
    String s = (String) obj;
    return "String: " + s.length();
} else if (obj instanceof Integer) {
    Integer i = (Integer) obj;
    return "Integer: " + i;
}
Enter fullscreen mode Exit fullscreen mode

Pattern matching collapses this:

// Java 16+ - Pattern matching
if (obj instanceof String s) {
    return "String: " + s.length();
} else if (obj instanceof Integer i) {
    return "Integer: " + i;
}
Enter fullscreen mode Exit fullscreen mode

Java 17 brings these together: pattern matching + switch expressions create powerful, type-safe conditional logic.


What is Pattern Matching?

Pattern matching is structural decomposition combined with conditional dispatch. You're asking:

  1. "What shape is this data?"
  2. "If it matches, bind the components to names I can use"

Type Patterns (Java 16+)

if (obj instanceof String s) {
    System.out.println(s.length());
}
Enter fullscreen mode Exit fullscreen mode

Guarded Patterns (Java 16+)

if (obj instanceof String s && s.length() > 5) {
    return s.toUpperCase();
}
Enter fullscreen mode Exit fullscreen mode

Pattern Matching in Switch (Java 17+)

String result = switch (obj) {
    case String s -> "String: " + s;
    case Integer i -> "Integer: " + i;
    case null -> "Null";
    default -> "Unknown";
};
Enter fullscreen mode Exit fullscreen mode

Real-World Example 1: equals() Implementation

Before Java 16:

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    Point other = (Point) obj;
    return this.x == other.x && this.y == other.y;
}
Enter fullscreen mode Exit fullscreen mode

Java 16+ with pattern matching:

@Override
public boolean equals(Object obj) {
    return obj instanceof Point p &&
           this.x == p.x && this.y == p.y;
}
Enter fullscreen mode Exit fullscreen mode

Real-World Example 2: Switch Expressions

Before Java 14:

String dayType;
switch (day) {
    case MONDAY: case TUESDAY: case WEDNESDAY:
    case THURSDAY: case FRIDAY:
        dayType = "Weekday";
        break;
    case SATURDAY: case SUNDAY:
        dayType = "Weekend";
        break;
}
Enter fullscreen mode Exit fullscreen mode

Java 14+ with arrow syntax:

String dayType = switch (day) {
    case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
    case SATURDAY, SUNDAY -> "Weekend";
};
Enter fullscreen mode Exit fullscreen mode

Real-World Example 3: Pattern Matching with Sealed Types

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

int area = switch (shape) {
    case Circle c -> (int) (Math.PI * c.radius() * c.radius());
    case Rectangle r -> r.width() * r.height();
    case Triangle t -> (t.base() * t.height()) / 2;
};
Enter fullscreen mode Exit fullscreen mode

Real-World Example 4: Guarded Patterns

String classify = switch (obj) {
    case String s when s.isEmpty() -> "Empty string";
    case String s when s.length() < 5 -> "Short string: " + s;
    case String s -> "Long string: " + s;
    default -> "Not a string";
};
Enter fullscreen mode Exit fullscreen mode

Real-World Example 5: Result Pattern

sealed interface Result<T> permits Success, Failure {}
record Success<T>(T value) implements Result<T> {}
record Failure<T>(String error) implements Result<T> {}

public static <T> String describe(Result<T> result) {
    return switch (result) {
        case Success<T> s -> "Success: " + s.value();
        case Failure<T> f -> "Failure: " + f.error();
    };
}
Enter fullscreen mode Exit fullscreen mode

Key Insight

Pattern matching represents a paradigm shift toward declarative conditional logic. Combined with sealed types, the compiler enforces exhaustiveness at compile-time, preventing entire classes of runtime type errors.


Read the Full Article

Discover more about pattern matching and switch expressions:

  • Type patterns and scope rules
  • Switch expressions vs traditional switch
  • Guarded patterns with when clause
  • Record destructuring in patterns
  • Sealed types with exhaustiveness checking
  • Best practices and migration strategy

Full article: https://blog.9mac.dev/java-17-features-every-senior-developer-should-know-part-4-pattern-matching-and-switch-expressions


Clone and Run

git clone https://github.com/dawid-swist/blog-9mac-dev-code.git
cd blog-post-examples/java/2025-10-25-java17-features-every-senior-developer-should-know
../../gradlew test --tests *part4*
Enter fullscreen mode Exit fullscreen mode

Part of the "Java 17 Features Every Senior Developer Should Know" series

  • Part 1: var Keyword
  • Part 2: Records
  • Part 3: Sealed Classes
  • Part 4: Pattern Matching & Switch (You are here)
  • Part 5: Text Blocks
  • Part 6: Syntax Cheat Sheet

Java #Java17 #PatternMatching #SwitchExpressions #Programming

Top comments (0)