Java 17 Sealed Classes Tutorial with Examples
Introduction
In Java, a class is either open to extension by anyone, or
OUTPUT of the above code
Explanation of the code
Sealed interfaces and records
Interfaces can be sealed too, and they combine naturally with recordsClick to read tutorial: Java 16 Records Tutorial with Examples, which are implicitly
OUTPUT of the above code
A method returning
OUTPUT on compiling the above code
The compiler rejects a permitted subclass which does not say how the hierarchy continues, and any class which is not in the
Why sealed classes matter: exhaustiveness
In the examples above, the code still needed a fallback - a
Summary
In this tutorial we saw how sealed classes and interfaces, final in Java 17, restrict which classes may extend or implement them with
final and closed to everyone. There was no way to say "this class can be extended, but only by these classes". Yet many designs need exactly that: a Shape which is a Circle, a Rectangle or a Triangle and nothing else, or a payment result which is either a success or a failure. Sealed classes, previewed in Java 15 and 16 and finalized in Java 17Click to read tutorial: Overview of New Features in Java 17 (JEP 409), let a class or interface list exactly which classes may extend or implement it. This tutorial explains sealed classes and interfaces with examples - the sealed and permits keywords, the rules for subclasses, sealed interfaces with records, the compiler errors for breaking the rules, and how sealed types make code exhaustive.
Declaring a sealed class
A class is sealed with the sealed modifier, and its permitted subclasses are listed after permits. Every permitted subclass must then declare how it continues the hierarchy, with exactly one of three modifiers -
final- it cannot be extended further,sealed- it can be extended, but only by its own list of permitted subclasses, ornon-sealed- it is open again: any class may extend it.
A sealed class hierarchy
//SealedShapes.java
public class SealedShapes {
static abstract sealed class Shape permits Circle, Rectangle, Polygon { }
static final class Circle extends Shape {
final double radius;
Circle(double radius) { this.radius = radius; }
}
static sealed class Rectangle extends Shape permits Square {
final double width, height;
Rectangle(double width, double height) { this.width = width; this.height = height; }
}
static final class Square extends Rectangle {
Square(double side) { super(side, side); }
}
static non-sealed class Polygon extends Shape { } // open again
static class Hexagon extends Polygon { } // allowed: Polygon is non-sealed
static double area(Shape shape) {
if (shape instanceof Circle c) return Math.PI * c.radius * c.radius;
if (shape instanceof Rectangle r) return r.width * r.height;
return Double.NaN; // polygons: not computed here
}
public static void main(String[] args) {
Shape[] shapes = {new Circle(1), new Rectangle(2, 3), new Square(2), new Hexagon()};
for (Shape s : shapes) {
System.out.printf("%-9s area=%.2f%n", s.getClass().getSimpleName(), area(s));
}
System.out.println("Shape is sealed: " + Shape.class.isSealed());
for (Class<?> c : Shape.class.getPermittedSubclasses()) {
System.out.println(" permits " + c.getSimpleName());
}
}
}
Circle area=3.14 Rectangle area=6.00 Square area=4.00 Hexagon area=NaN Shape is sealed: true permits Circle permits Rectangle permits Polygon
Shapepermits exactly three subclasses. No other class - in this file or anywhere else - can extend it.Circleisfinal,Rectangleis itselfsealedand permits onlySquare, andPolygonisnon-sealed, soHexagon(or any other class) may extend it. This lets the author decide, branch by branch, where the hierarchy is closed.- Reflection knows about sealing:
Class.isSealed()andClass.getPermittedSubclasses()were added in Java 17. - The permitted subclasses must be in the same module as the sealed class - or, if it is not in a named module, in the same package. When they are in the same source file, as here, the
permitsclause may be left out and the compiler infers it.
final. Together they model data which is "one of a fixed set of shapes" - what other languages call an algebraic data type or a sum type -
A sealed interface implemented by records
//PaymentResults.java
public class PaymentResults {
sealed interface PaymentResult permits Approved, Declined, Pending { }
record Approved(String transactionId, long amountCents) implements PaymentResult { }
record Declined(String reason) implements PaymentResult { }
record Pending(int retryAfterSeconds) implements PaymentResult { }
static String message(PaymentResult result) {
if (result instanceof Approved a) return "Paid " + a.amountCents() / 100.0 + " (txn " + a.transactionId() + ")";
if (result instanceof Declined d) return "Declined: " + d.reason();
if (result instanceof Pending p) return "Pending - retry in " + p.retryAfterSeconds() + "s";
throw new AssertionError("impossible: PaymentResult is sealed");
}
public static void main(String[] args) {
PaymentResult[] results = {new Approved("TX-1001", 49_900), new Declined("insufficient funds"), new Pending(30)};
for (PaymentResult r : results) System.out.println(message(r));
}
}
Paid 499.0 (txn TX-1001) Declined: insufficient funds Pending - retry in 30s
PaymentResult tells its callers precisely which outcomes to handle - documented by the type itself, and checked by the compiler.
Rules and compiler errors
Breaking the rules of sealed classes
//SealedErrors.java
public class SealedErrors {
sealed interface Vehicle permits Car, Bike { }
final class Car implements Vehicle { }
class Bike implements Vehicle { } // missing final, sealed or non-sealed
final class Truck implements Vehicle { } // not in the permits list
}
SealedErrors.java:5: error: sealed, non-sealed or final modifiers expected
class Bike implements Vehicle { } // missing final, sealed or non-sealed
^
SealedErrors.java:6: error: class is not allowed to extend sealed class: Vehicle (as it is not listed in its 'permits' clause)
final class Truck implements Vehicle { } // not in the permits list
^
2 errors
permits list. The JVM enforces the same rules at runtime, so they cannot be bypassed by generating classes.
return Double.NaN or a throw new AssertionError() - because if statements cannot know that all cases are covered. Sealed types were designed to pair with pattern matching for switchClick to read: pattern matching for switch, finalized in Java 21. A switch over a sealed type is exhaustive when it has a case for every permitted subtype - no default is needed, and if a new subtype is added later, every switch which does not handle it becomes a compile error. In Java 21 the message() method becomes -
Java 21: an exhaustive switch over a sealed interface
static String message(PaymentResult result) {
return switch (result) {
case Approved a -> "Paid " + a.amountCents() / 100.0;
case Declined d -> "Declined: " + d.reason();
case Pending p -> "Pending - retry in " + p.retryAfterSeconds() + "s";
}; // no default: the compiler knows these are all the cases
}
sealed and permits, and how each permitted subclass continues the hierarchy as final, sealed or non-sealed. We modelled a fixed set of outcomes with a sealed interface and records, inspected sealing with reflection, and saw the compiler errors for breaking the rules. Lastly, we saw how sealed types make switches exhaustive from Java 21. Next, read about the new random number generators in Java 17Click to read tutorial: Java 17 Random Number Generators: RandomGenerator API.Continue Reading Java 12 to 17 Tutorials
Overview of New Features in Java 17Click to read tutorial: Overview of New Features in Java 17 Java 17 Random Number Generators: RandomGenerator APIClick to read tutorial: Java 17 Random Number Generators: RandomGenerator API Java 17 API Additions: HexFormat, InstantSource and MoreClick to read tutorial: Java 17 API Additions: HexFormat, InstantSource and More Overview of New Features in Java 12Click to read tutorial: Overview of New Features in Java 12 Overview of New Features in Java 13Click to read tutorial: Overview of New Features in Java 13 Overview of New Features in Java 14Click to read tutorial: Overview of New Features in Java 14 Overview of New Features in Java 15Click to read tutorial: Overview of New Features in Java 15 Overview of New Features in Java 16Click to read tutorial: Overview of New Features in Java 16 Java 12 API Additions: String.indent, teeing, mismatchClick to read tutorial: Java 12 API Additions: String.indent, teeing, mismatch
Overview of New Features in Java 17Click to read tutorial: Overview of New Features in Java 17 Java 17 Random Number Generators: RandomGenerator APIClick to read tutorial: Java 17 Random Number Generators: RandomGenerator API Java 17 API Additions: HexFormat, InstantSource and MoreClick to read tutorial: Java 17 API Additions: HexFormat, InstantSource and More Overview of New Features in Java 12Click to read tutorial: Overview of New Features in Java 12 Overview of New Features in Java 13Click to read tutorial: Overview of New Features in Java 13 Overview of New Features in Java 14Click to read tutorial: Overview of New Features in Java 14 Overview of New Features in Java 15Click to read tutorial: Overview of New Features in Java 15 Overview of New Features in Java 16Click to read tutorial: Overview of New Features in Java 16 Java 12 API Additions: String.indent, teeing, mismatchClick to read tutorial: Java 12 API Additions: String.indent, teeing, mismatch