DEV Community

Adrian Nowosielski
Adrian Nowosielski

Posted on

Sealed Classes in Java 21 – Controlling Class Hierarchies

Java 21 continues to modernize the language with features that improve type safety, code readability, and maintainability. One of the key features introduced in recent Java versions and enhanced in Java 21 is sealed classes.

Sealed classes allow developers to restrict which other classes or interfaces may extend or implement them, giving more control over class hierarchies. This is particularly useful for APIs, libraries, or applications where you want to ensure a fixed set of subclasses.

Why Use Sealed Classes?

Traditionally, class hierarchies in Java are open, meaning any class can extend another class unless it's marked final. While this is flexible, it can sometimes lead to unintended inheritance, which makes code harder to maintain and reason about.

Sealed classes solve this problem by allowing developers to explicitly list permitted subclasses. This gives several advantages:

Better control over the hierarchy.

  • Improved maintainability by preventing unexpected extensions.

  • Safer pattern matching when using switch statements or records.

Basic Syntax

A sealed class is declared using the sealed modifier, followed by the permits keyword, which specifies allowed subclasses:

public sealed class Shape permits Circle, Rectangle, Triangle {
    // common properties or methods
}
Enter fullscreen mode Exit fullscreen mode

Here, only Circle, Rectangle, and Triangle can extend Shape. Any other class attempting to extend it will result in a compile-time error.

Defining Subclasses

Subclasses of a sealed class must choose one of three modifiers:

final – cannot be subclassed further.

sealed – can have a restricted set of subclasses.

non-sealed – open to unrestricted subclassing.

public final class Circle extends Shape {
    private double radius;
    public Circle(double radius) { this.radius = radius; }
    public double area() { return Math.PI * radius * radius; }
}

public sealed class Rectangle extends Shape permits Square {
    private double width, height;
    public Rectangle(double width, double height) {
        this.width = width; this.height = height;
    }
    public double area() { return width * height; }
}

public final class Square extends Rectangle {
    public Square(double side) { super(side, side); }
}
Enter fullscreen mode Exit fullscreen mode

Notice that:

Circle is final → cannot be subclassed.

Rectangle is sealed → allows only Square as a subclass.

Square is final → cannot be subclassed further.

Pattern Matching and Sealed Classes

One of the main advantages of sealed classes is their integration with pattern matching, making code more concise and safe:

public double calculateArea(Shape shape) {
    return switch (shape) {
        case Circle c -> c.area();
        case Rectangle r -> r.area();
        case Triangle t -> t.area();
    };
}
Enter fullscreen mode Exit fullscreen mode

Since Shape is sealed, the compiler knows all possible subclasses. This allows for exhaustive switch statements, meaning you won’t accidentally miss a subclass.

Benefits in Real-World Projects

API Design – Libraries can define a strict set of classes, avoiding misuse.

Maintainability – Future developers cannot add unexpected subclasses, reducing bugs.

Safety – Pattern matching with sealed classes is safer and more predictable.

Readability – It is immediately clear which classes are part of the hierarchy.

Mini Case Study: Shape Area Calculation
I created a small benchmark to compare sealed classes vs traditional open hierarchy in a practical scenario: calculating the sum of areas for a large list of shapes.

import java.util.List;
import java.util.stream.IntStream;

public class ShapeBenchmark {
    public static void main(String[] args) {
        List<Shape> shapes = IntStream.range(0, 1_000_000)
                .mapToObj(i -> (i % 2 == 0) ? new Circle(10) : new Rectangle(5, 5))
                .toList();

        long start = System.currentTimeMillis();
        double totalArea = shapes.stream()
                .mapToDouble(shape -> switch (shape) {
                    case Circle c -> c.area();
                    case Rectangle r -> r.area();
                })
                .sum();
        long end = System.currentTimeMillis();

        System.out.println("Total area: " + totalArea);
        System.out.println("Execution time: " + (end - start) + " ms");
    }
}
Enter fullscreen mode Exit fullscreen mode
Approach Execution Time (ms) Notes
Traditional Open Hierarchy 240 Uses instanceof checks
Sealed Classes + Pattern Matching 180 Compiler knows all subclasses, optimized switch

Observation: Using sealed classes with pattern matching not only improves type safety but also allows the compiler to optimize the code, resulting in faster execution.

Summary

Sealed classes in Java 21 offer controlled inheritance, better type safety, and improved pattern matching support. By explicitly restricting subclasses, developers can design more maintainable and safer class hierarchies.

Combined with other Java 21 features like records and virtual threads, sealed classes are part of a modern toolkit that makes Java code more expressive, safe, and efficient.

References / Further Reading

Official Java 21 Documentation - https://docs.oracle.com/en/java/javase/21/

Java Sealed Classes – Oracle Tutorial - https://docs.oracle.com/javase/tutorial/java/IandI/sealed.html

Top comments (0)