Home/Learn/Java A–Z/Sealed Classes

Sealed Classes

Intermediate
Modern Java

Sealed classes restrict which classes can extend or implement them, enabling exhaustive pattern matching and closed hierarchies.

Overview

Sealed classes, introduced in Java 17, let you explicitly declare which classes or interfaces are permitted to extend or implement a type. This creates a closed hierarchy that the compiler can reason about exhaustively — enabling complete switch expressions and pattern matching. Sealed classes combine well with records and patterns to model algebraic data types cleanly in Java.

Declaring Sealed Classes

A sealed class uses the sealed modifier and a permits clause listing allowed subtypes. Each permitted subtype must be in the same package (or module) and must declare itself as final, sealed, or non-sealed.

final means no further extension. sealed means it further restricts its own subtypes. non-sealed reopens the hierarchy — any class can extend it.

Shape.java
// Sealed hierarchy for shapes
public sealed class Shape
    permits Circle, Rectangle, Triangle {}

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

public final class Rectangle extends Shape {
    private final double width, height;
    public Rectangle(double width, double height) {
        this.width = width; this.height = height;
    }
    public double width() { return width; }
    public double height() { return height; }
}

public non-sealed class Triangle extends Shape {
    // can be extended freely
}

Sealed Interfaces

Interfaces can also be sealed. This is particularly useful for modelling command or result types where you want an exhaustive set of implementations.

Combining sealed interfaces with records gives you a concise algebraic data type (ADT) style that is very common in functional languages.

Result.java
public sealed interface Result<T>
    permits Result.Success, Result.Failure {

  record Success<T>(T value) implements Result<T> {}
  record Failure<T>(String error) implements Result<T> {}
}

// Usage
Result<Integer> r = new Result.Success<>(42);

String msg = switch (r) {
    case Result.Success<Integer> s -> "Value: " + s.value();
    case Result.Failure<Integer> f -> "Error: " + f.error();
};

Exhaustive Switch with Sealed Types

When you switch over a sealed type and cover every permitted subtype, the compiler knows the switch is exhaustive — no default branch needed. This is one of the main motivations for sealed classes.

If you add a new permitted type later, all exhaustive switches become compile errors, guiding you to handle the new case.

ShapeArea.java
public double area(Shape shape) {
    return switch (shape) {
        case Circle c    -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.width() * r.height();
        case Triangle t  -> 0.5 * t.base() * t.height();
        // No default needed — compiler knows all subtypes
    };
}

Key Points to Remember

  • sealed restricts which classes can extend or implement a type using permits.
  • Permitted subtypes must be final, sealed, or non-sealed.
  • Sealed hierarchies enable exhaustive switch expressions without a default.
  • Sealed interfaces + records = concise algebraic data types in Java.
  • All permitted subtypes must reside in the same package or module.

Practice Sealed Classes in the Playground

Run and modify code directly in your browser - no setup needed.

Interview Questions

Sign in to ask Aria
1

What problem do sealed classes solve that was previously handled with package-private constructors?

MediumOracle
2

What is the difference between final, sealed, and non-sealed subclasses?

MediumGoogle
3

How do sealed classes improve switch expression exhaustiveness?

MediumAmazon
4

Can a record implement a sealed interface?

EasyMicrosoft
5

What restrictions apply to the location of permitted subtypes?

MediumTCS

Ask Aria about Sealed Classes

Your personal AI tutor — ask anything about this concept