4.3 Sealed Classes and Interfaces: permits, final, non-sealed, and Hierarchy Architecture

Key Takeaways

  • Sealed classes and interfaces restrict their inheritance hierarchy by explicitly declaring allowed direct subtypes using the sealed keyword and a permits clause.
  • Every direct permitted subtype must explicitly declare exactly one modifier: final (closes hierarchy), sealed (continues restricted hierarchy), or non-sealed (opens hierarchy for arbitrary extension).
  • In unnamed modules (classpath), permitted subtypes must reside in the exact same package; in named modules, they must reside within the same module across any packages.
  • When all permitted subtypes are declared within the same source file (.java compilation unit), the permits clause can be omitted entirely and is inferred by the compiler.
Last updated: September 2026

Sealed Classes and Interfaces: permits, final, non-sealed, and Hierarchy Architecture

Standardized in Java 17 (JEP 409) and central to compiler-enforced pattern matching in Java 21, sealed classes and interfaces allow developers to explicitly define and restrict the set of permitted subclasses or implementing types.

Prior to the introduction of sealed types, Java provided only two extremes for inheritance control: completely open inheritance (any class can extend a public class) or completely prohibited inheritance (final class). Developers relied on brittle workarounds like package-private constructors to limit subclassing. Sealed types bridge this architectural gap, enabling domain models to declare closed, well-defined algebraic hierarchies that the compiler can analyze for safety and exhaustiveness.


1. Sealed Class and Interface Declarations

A class or interface is sealed using the sealed modifier accompanied by a permits clause listing all authorized direct subtypes:

// Sealed class permitting exactly three direct subclasses
public sealed class Shape permits Circle, Rectangle, Triangle {
    public abstract double area();
}

// Sealed interface permitting two implementing types
public sealed interface PaymentMethod permits CreditCard, BankTransfer {
    void processPayment(double amount);
}

Core Rules for the permits Clause

  1. Direct Subtypes Only: Every type listed in the permits clause must directly extend the sealed class (or directly implement the sealed interface). Listing indirect subtypes (e.g. grandchildren) causes a compile-time error.
  2. Bilateral Inheritance Contract: If class Shape permits Circle, then Circle must explicitly extend Shape in its declaration. If Circle fails to extend Shape, or if Circle extends Shape without being listed in permits, compilation fails.
  3. Co-location / Accessibility Boundaries:
    • In Unnamed Modules (Classpath): The sealed supertype and all its permitted subtypes must reside in the exact same package.
    • In Named Modules (JPMS): The sealed supertype and all its permitted subtypes must reside in the same named module (they are permitted to span different packages within that module, provided they are accessible).

2. Mandatory Modifiers for Permitted Subclasses

Every direct permitted subclass of a sealed class must explicitly declare exactly one of the following three modifiers:

                    ┌───> final (Subclassing terminates completely)
                    │
Permitted Subclass ─┼───> sealed (Continues sealed restriction; must declare permits)
                    │
                    └───> non-sealed (Re-opens inheritance hierarchy to any arbitrary class)
// Option 1: final - prevents any further subclassing
public final class Circle extends Shape {
    private final double radius;
    public Circle(double radius) { this.radius = radius; }
    @Override public double area() { return Math.PI * radius * radius; }
}

// Option 2: sealed - continues fine-grained subclass restriction
public sealed class Rectangle extends Shape permits Square, Oblong {
    private final double width, height;
    public Rectangle(double width, double height) { this.width = width; this.height = height; }
    @Override public double area() { return width * height; }
}

// Option 3: non-sealed - opens the hierarchy for unrestricted extension
public non-sealed class Triangle extends Shape {
    private final double base, height;
    public Triangle(double base, double height) { this.base = base; this.height = height; }
    @Override public double area() { return 0.5 * base * height; }
}

Critical Subclass Constraints

  • Prohibition of Default Modifiers: Omitting all three modifiers (e.g., public class Circle extends Shape) causes an immediate compile-time error (class must be sealed, non-sealed or final).
  • The non-sealed Keyword: non-sealed is the first hyphenated keyword in Java. It explicitly signals that a subclass opts out of sealed hierarchy restrictions, allowing arbitrary downstream classes to extend it.
  • Abstract Permitted Classes: If a permitted subclass is declared abstract, it cannot be marked final (because abstract and final are mutually contradictory). It must be marked either sealed or non-sealed:
    public abstract sealed class Polygon extends Shape permits Hexagon, Octagon {}
    

3. Omitting the permits Clause (Same-File Subclasses)

If all permitted subclasses are declared within the same source file (.java compilation unit) as the sealed supertype (either as nested classes or as peer top-level classes in the same file), the permits clause can be omitted entirely:

// In File: Expr.java
public sealed interface Expr {
    // Compiler automatically infers: permits Constant, Add, Multiply
    
    final class Constant implements Expr {
        private final int value;
        public Constant(int value) { this.value = value; }
        public int value() { return value; }
    }
    
    final class Add implements Expr {
        private final Expr left, right;
        public Add(Expr left, Expr right) { this.left = left; this.right = right; }
        public Expr left() { return left; }
        public Expr right() { return right; }
    }
    
    final class Multiply implements Expr {
        private final Expr left, right;
        public Multiply(Expr left, Expr right) { this.left = left; this.right = right; }
        public Expr left() { return left; }
        public Expr right() { return right; }
    }
}

When javac processes a sealed class or interface lacking a permits clause, it inspects the enclosing compilation unit and automatically derives the list of permitted subtypes.


4. Integration with Records and Enums

Sealed types integrate seamlessly with records and enums to construct clean algebraic data types (ADTs):

1. Records in Sealed Hierarchies

Because records are implicitly final, a record implementing a sealed interface or extending a sealed permits list does not require an explicit final modifier:

public sealed interface JSONValue permits JSONString, JSONNumber, JSONObject, JSONNull {}

public record JSONString(String value) implements JSONValue {} // Implicitly final!
public record JSONNumber(double value) implements JSONValue {} // Implicitly final!
public record JSONObject(Map<String, JSONValue> members) implements JSONValue {} // Implicitly final!
public record JSONNull() implements JSONValue {} // Implicitly final!

2. Enums in Sealed Hierarchies

Enums are implicitly final (or implicitly sealed if they declare constant-specific class bodies). An enum can directly implement a sealed interface:

public sealed interface OperationResult permits SuccessCode, ErrorPayload {}

public enum SuccessCode implements OperationResult {
    OK, ACCEPTED, CREATED // Implicitly final!
}

public record ErrorPayload(int code, String message) implements OperationResult {}

5. Reflection API for Sealed Classes

Java SE 21 provides dedicated reflection methods in java.lang.Class to inspect sealed type metadata at runtime:

Class<?> clazz = Shape.class;

// 1. Check if the type is sealed
boolean isSealed = clazz.isSealed(); // Returns true

// 2. Retrieve array of permitted subclasses (returns Class<?>[] or null if not sealed)
Class<?>[] permittedClasses = clazz.getPermittedSubclasses();
for (Class<?> sub : permittedClasses) {
    System.out.println("Permitted subtype: " + sub.getName());
}

6. Summary of Key Exam Traps for Sealed Types

ScenarioCode ExampleOutcome / Exam Trap
Missing Subclass Modifierclass Sub extends SealedBaseCompilation Error: Every direct permitted subclass must be final, sealed, or non-sealed.
Abstract Final Subclassabstract final class Sub extends SealedBaseCompilation Error: abstract and final cannot be combined on any class.
Cross-Package Subclassing (Classpath)Subclass in pkgB extending sealed class in pkgACompilation Error: Permitted subtypes on classpath must reside in the exact same package.
Missing Permitted Subclasssealed class A permits B (but B doesn't exist or extend A)Compilation Error: Bilateral contract requires B to exist and declare extends A.
Redundant Final on Recordpublic final record R() implements SealedIValid Syntax: But final is redundant because records are implicitly final.
Indirect Subclass in Permitssealed class A permits GrandChildCompilation Error: Only direct subclasses may be listed in permits.
Loading diagram...
Sealed Class Hierarchy Transitions and Subclass Modifiers
Test Your Knowledge

Given the following class declarations in the same package:

public sealed class Transport permits Car, Boat {}

public class Car extends Transport {}

public final class Boat extends Transport {}
What is the result of attempting to compile these classes?

A
B
C
D
Test Your Knowledge

Consider the following single compilation unit (MathNode.java):

public sealed interface MathNode {
    record Number(double val) implements MathNode {}
    record Negate(MathNode node) implements MathNode {}
}
Why does this code compile successfully despite having no explicit permits clause?

A
B
C
D
Test Your Knowledge

A developer attempts to declare an abstract subclass extending a sealed superclass:

public sealed class Service permits CloudService, EdgeService {}

// Line X
public abstract class CloudService extends Service {}
Which modifier MUST be added at Line X alongside abstract for the code to compile?

A
B
C
D
Test Your Knowledge

Which of the following statements is TRUE regarding records implementing sealed interfaces?

A
B
C
D