Sealed Classes and Interfaces in Java

Naveen AnamaneniUpdated · 6 min read

A payment in a shop is UPI, card or cash, and nothing else. With an ordinary interface, any class anywhere could implement Payment, so code that handles "every kind of payment" can never be sure it really has. A sealed class or interface (standard since Java 17) lists its permitted subtypes. The compiler then knows the complete set, rejects anything else, and can check that a switch handles every case, with no default needed.

A Closed Set of Shapes

The permits list is the complete set of subtypes
Shapes.java▶ Try it
public class Shapes {
    sealed interface Shape permits Circle, Square, Rect { }
 
    record Circle(double r) implements Shape { }
    record Square(double side) implements Shape { }
    record Rect(double w, double h) implements Shape { }
 
    static double area(Shape s) {
        return switch (s) {
            case Circle c -> Math.PI * c.r() * c.r();
            case Square q -> q.side() * q.side();
            case Rect r -> r.w() * r.h();
        };
    }
 
    public static void main(String[] args) {
        Shape[] shapes = { new Circle(1), new Square(4), new Rect(2, 3) };
        for (Shape s : shapes) {
            System.out.printf("%s -> %.2f%n", s, area(s));
        }
    }
}
Output
Circle[r=1.0] -> 3.14
Square[side=4.0] -> 16.00
Rect[w=2.0, h=3.0] -> 6.00

How it works

  1. sealed interface Shape permits Circle, Square, Rect says exactly three types may implement Shape.
  2. Each is a record, and records are implicitly final, which satisfies the sealing rules below.
  3. The switch uses type patterns: case Circle c matches a Circle and names it c (a later lesson covers patterns fully).
  4. Because the compiler knows Shape has exactly three subtypes, and all three are handled, the switch is exhaustive: no default needed.

The Rules

Every permitted subclass must say how it continues the hierarchy, with exactly one of three modifiers:

Modifier on the subclassMeaning
finalNo further subclasses (records and enums are implicitly final)
sealedIt has its own permits list
non-sealedOpen again: anyone may extend it
Payments
sealed abstract class Payment permits Upi, Card, Cash { }
final class Upi extends Payment { }                  // closed
sealed class Card extends Payment permits Credit, Debit { }
final class Credit extends Card { }
final class Debit extends Card { }
non-sealed class Cash extends Payment { }            // open to extension

Further rules:

  • Permitted subclasses must be in the same module (or, without modules, the same package) as the sealed type.
  • If all subclasses are in the same file, the permits clause can be left out; the compiler infers it.

The Errors

A class that isn't listed:

S1.java
sealed interface Shape permits Circle, Square { }
record Circle(double r) implements Shape { }
record Square(double side) implements Shape { }
record Triangle(double b, double h) implements Shape { }
Compiler output
S1.java:4: error: class is not allowed to extend sealed class: Shape (as it is not listed in its 'permits' clause)
record Triangle(double b, double h) implements Shape { }
^
1 error

A permitted subclass with no modifier:

S2.java
sealed abstract class Payment permits Upi, Card { }
final class Upi extends Payment { }
class Card extends Payment { }
Compiler output
S2.java:3: error: sealed, non-sealed or final modifiers expected
class Card extends Payment { }
^
1 error

And if a switch forgets a permitted type, it no longer compiles: error: the switch expression does not cover all possible input values. Add a new shape to the permits list and every switch that doesn't handle it breaks at compile time, pointing you to exactly the code that needs updating.

Common Mistakes

Forgetting final, sealed or non-sealed on a permitted class. Records and enums get final automatically; ordinary classes don't.

Adding a default to an exhaustive switch. It works, but it silences the compiler error you'd want when a new subtype appears.

Permitting classes in another package without modules. Keep the hierarchy together.

Interview Corner

What are sealed classes, and why were they added?

Sealed classes and interfaces, standard since Java 17, restrict which classes can extend or implement them through a permits clause. Each permitted subclass must be final, sealed or non-sealed. They let a library author model a closed set of alternatives, keep the hierarchy under control, and let the compiler check that switch statements and expressions over the sealed type are exhaustive.

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

A final class can't be extended at all. A sealed class can be extended only by the classes listed in its permits clause. A non-sealed class is a permitted subclass that re-opens the hierarchy, so any class may extend it.

Practice

  1. Easy: Model sealed interface Payment permits Upi, Card, Cash with three records, and write a method that returns a description for each using a switch.
  2. Harder: Add a Wallet record to Payment's permits list without updating the switch. Read the compile error, then fix it.

Quick check: Sealed types

4 questions
1What does permits list?
2Which modifier must a permitted ordinary class have?
3Why can a switch over a sealed type skip default?
4Since which version are sealed classes standard?

Key Takeaways

Was this tutorial helpful?

Lesson 176 of 482