Code Snippets
/

Sealed Interfaces with Pattern Matching

Sealed Interfaces with Pattern Matching

Sealed interfaces (Java 17+) plus switch pattern matching (Java 21+) give Java algebraic-data-type ergonomics: a closed family of subtypes that the compiler can check exhaustively in one switch. This snippet shows the modern syntax in comments and a Java 13-compatible equivalent using a visitor-style abstract class hierarchy that compiles in our test runner. Use this pattern for tagged unions like `Result<Ok, Err>`, AST nodes, and state machines.

Java
Hard
java-sealed-classes
java-pattern-matching
type-system
design-patterns

1,112 views

24

// Java 17+ syntax (cannot run on JDK 13):
//
//   public sealed interface Shape permits Circle, Square, Triangle {}
//   public record Circle(double r) implements Shape {}
//   public record Square(double side) implements Shape {}
//   public record Triangle(double base, double height) implements Shape {}
//
// Below is the JDK 13 equivalent: an abstract class with package-private
// constructor and a fixed set of final subclasses.

abstract class Shape {
    private Shape() {} // private ctor + final subclasses == effectively sealed
    static final class Circle extends Shape { final double r;
        Circle(double r) { this.r = r; } }
    static final class Square extends Shape { final double side;
        Square(double s) { this.side = s; } }
    static final class Triangle extends Shape { final double base, height;
        Triangle(double b, double h) { this.base = b; this.height = h; } }
}

public class Main {
    public static void main(String[] args) {
        Shape[] shapes = {
            new Shape.Circle(2),
            new Shape.Square(3),
            new Shape.Triangle(4, 5)
        };
        for (Shape s : shapes) System.out.printf("area=%.2f%n", area(s));
    }

    static double area(Shape s) {
        // Java 21+ pattern-matching switch (cannot run on JDK 13):
        //
        //   return switch (s) {
        //       case Circle c   -> Math.PI * c.r() * c.r();
        //       case Square sq  -> sq.side() * sq.side();
        //       case Triangle t -> 0.5 * t.base() * t.height();
        //   };
        //
        // JDK 13 equivalent uses instanceof + cast:
        if (s instanceof Shape.Circle) {
            Shape.Circle c = (Shape.Circle) s;
            return Math.PI * c.r * c.r;
        }
        if (s instanceof Shape.Square) {
            Shape.Square sq = (Shape.Square) s;
            return sq.side * sq.side;
        }
        Shape.Triangle t = (Shape.Triangle) s;
        return 0.5 * t.base * t.height;
    }
}

A sealed interface declares a closed set of allowed implementers via the permits clause; once published, no other class can extend it without modifying the source. That promise is what enables exhaustive checking: the Java 21 compiler verifies that a switch over a sealed type covers every permitted subtype and refuses to compile otherwise. The runtime trick that simulates sealing on older JDKs is a private constructor plus final static nested subclasses, which prevents external extension while still allowing the type to act as a discriminator. The pattern is the foundation for tagged unions and is the cleanest way to model finite alternatives like Result<T, E> or a small state machine.

2 more snippets in this entry are available for premium members.

Upgrade to Premium