Java Sealed Classes in JDK 17
History
Java Sealed Classes were proposed by JEP 360 and delivered in JDK 15 as a preview feature. They were proposed again, with refinements, by JEP 397 and delivered in JDK 16 as a preview feature. This JEP proposes to finalize Sealed Classes in JDK 17, with no changes from JDK 16.
Overview
Enhance the Java programming language with sealed classes and interfaces. Java Sealed classes and interfaces restrict which other classes or interfaces may extend or implement them.
One of the primary purposes of inheritance is code reuse: When you want to create a new class and there is already a class that includes some of the code that you want, you can derive your new class from the existing class. In doing this, you can reuse the fields and methods of the existing class without having to write (and debug) them yourself.
However, what if you want to model the various possibilities that exist in a domain by defining its entities and determining how these entities should relate to each other? For example, you’re working on a graphics library. You want to determine how your library should handle common geometric primitives like circles and squares. You’ve created a Shape
class that these geometric primitives can extend. However, you’re not interested in allowing any arbitrary class to extend Shape
; you don’t want clients of your library declaring any further primitives. By sealing a class, you can specify which classes are permitted to extend it and prevent any other arbitrary class from doing so.

Goals
This JDK 17 feature java sealed classes will allow the author of a class or interface to control which code is responsible for implementing it.
Provides a more declarative way than access modifiers to restrict the use of a superclass.
Support future directions in pattern matching by providing a foundation for the exhaustive analysis of patterns.
Non-Goals
It is not a goal to provide new forms of access control such as “friends”.
It is not a goal to change
final
in any way, so no need to get confused.
Declaring Java Sealed Classes
To seal a class, add the sealed
modifier to its declaration. Then, after any extends
and implements
clauses, add the permits
clause. This clause specifies the classes that may extend the sealed class.
For example, the following declaration of Shape
specifies three permitted subclasses, Circle
, Square
, and Rectangle
:
Figure 3-1 Shape.java
public sealed class Shape
permits Circle, Square, Rectangle {
}
Define the following three permitted subclasses, Circle
, Square
, and Rectangle
, in the same module or in the same package as the sealed class:
Figure 3-2 Circle.java
public final class Circle extends Shape {
public float radius;
}
Figure 3-3 Square.java
Square
is a non-sealed class. This type of class is explained in Constraints on Permitted Subclasses.
public non-sealed class Square extends Shape {
public double side;
}
Figure 3-4 Rectangle.java
public sealed class Rectangle extends Shape permits FilledRectangle {
public double length, width;
}
Rectangle
has a further subclass, FilledRectangle
:
Figure 3-5 FilledRectangle.java
public final class FilledRectangle extends Rectangle {
public int red, green, blue;
}
Alternatively, you can define permitted subclasses in the same file as the sealed class. If you do so, then you can omit the permits
clause:
package com.techshitanshu.geometry;
public sealed class Figure
// The permits clause has been omitted
// as its permitted classes have been
// defined in the same file.
{ }
final class Circle extends Figure {
float radius;
}
non-sealed class Square extends Figure {
float side;
}
sealed class Rectangle extends Figure {
float length, width;
}
final class FilledRectangle extends Rectangle {
int red, green, blue;
}
Constraints on Permitted Subclasses
Permitted subclasses have the following constraints:
They must be accessible by the sealed class at compile time.
For example, to compile
Shape.java
, the compiler must be able to access all of the permitted classes ofShape
:Circle.java
,Square.java
, andRectangle.java
. In addition, becauseRectangle
is a sealed class, the compiler also needs access toFilledRectangle.java
.They must directly extend the sealed class.
They must have exactly one of the following modifiers to describe how it continues the sealing initiated by its superclass:
final
: Cannot be extended furthersealed
: Can only be extended by its permitted subclassesnon-sealed
: Can be extended by unknown subclasses; a sealed class cannot prevent its permitted subclasses from doing this
For example, the permitted subclasses of
Shape
demonstrate each of these three modifiers:Circle
isfinal
whileRectangle
is sealed andSquare
isnon-sealed
.They must be in the same module as the sealed class (if the sealed class is in a named module) or in the same package (if the sealed class is in the unnamed module, as in the
Shape.java
example).For example, in the following declaration of
com.example.graphics.Shape
, its permitted subclasses are all in different packages. This example will compile only ifShape
and all of its permitted subclasses are in the same named module.
Declaring Sealed Interfaces
Like sealed classes, to seal an interface, add the sealed
modifier to its declaration. Then, after any extends
clause, add the permits
clause, which specifies the classes that can implement the sealed interface and the interfaces that can extend the sealed interface.
The following example declares a sealed interface named Expr
. Only the classes ConstantExpr
, PlusExpr
, TimesExpr
, and NegExpr
may implement it:
package com.example.expressions;
public class TestExpressions {
public static void main(String[] args) {
// (6 + 7) * -8
System.out.println(
new TimesExpr(
new PlusExpr(new ConstantExpr(6), new ConstantExpr(7)),
new NegExpr(new ConstantExpr(8))
).eval());
}
}
sealed interface Expr
permits ConstantExpr, PlusExpr, TimesExpr, NegExpr {
public int eval();
}
final class ConstantExpr implements Expr {
int i;
ConstantExpr(int i) { this.i = i; }
public int eval() { return i; }
}
final class PlusExpr implements Expr {
Expr a, b;
PlusExpr(Expr a, Expr b) { this.a = a; this.b = b; }
public int eval() { return a.eval() + b.eval(); }
}
final class TimesExpr implements Expr {
Expr a, b;
TimesExpr(Expr a, Expr b) { this.a = a; this.b = b; }
public int eval() { return a.eval() * b.eval(); }
}
final class NegExpr implements Expr {
Expr e;
NegExpr(Expr e) { this.e = e; }
public int eval() { return -e.eval(); }
}
Narrowing Reference Conversion and Disjoint Types
Narrowing reference conversion is one of the conversions used in type checking cast expressions. It enables an expression of a reference type S
to be treated as an expression of a different reference type T
, where S
is not a subtype of T
. A narrowing reference conversion may require a test at run time to validate that a value of type S
is a legitimate value of type T
. However, there are restrictions that prohibit conversion between certain pairs of types when it can be statically proven that no value can be of both types.
APIs Related to Sealed Classes and Interfaces
The class java.lang.Class has two new methods related to sealed classes and interfaces:
- java.lang.constant.ClassDesc[] permittedSubclasses(): Returns an array containing java.lang.constant.ClassDesc objects representing all the permitted subclasses of the class if it is sealed; returns an empty array if the class is not sealed
- boolean isSealed(): Returns true if the given class or interface is sealed; returns false otherwise
Sealed classes and pattern matching
A significant benefit of sealed classes will be realized in JEP 406, which proposes to extend switch
with pattern matching. Instead of inspecting an instance of a sealed class with if
–else
chains, user code will be able to use a switch
enhanced with patterns. The use of sealed classes will allow the Java compiler to check that the patterns are exhaustive.
For example, consider this code using the sealed
hierarchy declared earlier:
Shape rotate(Shape shape, double angle) {
if (shape instanceof Circle) return shape;
else if (shape instanceof Rectangle) return shape;
else if (shape instanceof Square) return shape;
else throw new IncompatibleClassChangeError();
}
The Java compiler cannot ensure that the instanceof
tests cover all the permitted subclasses of Shape
. The final else
clause is actually unreachable, but this cannot be verified by the compiler. More importantly, no compile-time error message would be issued if the instanceof Rectangle
test was omitted.
In contrast, with pattern matching for switch
(JEP 406)the compiler can confirm that every permitted subclass of Shape
is covered, so no default
clause or other total pattern is needed. The compiler will, moreover, issue an error message if any of the three cases is missing:
Shape rotate(Shape shape, double angle) {
return switch (shape) { // pattern matching switch
case Circle c -> c;
case Rectangle r -> shape.rotate(angle);
case Square s -> shape.rotate(angle);
// no default needed!
}
}
JVM support for sealed classes
The Java Virtual Machine recognizes sealed
classes and interfaces at runtime, and prevents extension by unauthorized subclasses and subinterfaces.
Although sealed
is a class modifier, there is no ACC_SEALED
flag in the ClassFile
structure. Instead, the class
file of a sealed class has a PermittedSubclasses
attribute which implicitly indicates the sealed
modifier and explicitly specifies the permitted subclasses:
PermittedSubclasses_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_classes;
u2 classes[number_of_classes];
}
The list of permitted subclasses is mandatory. Even when the permitted subclasses are inferred by the compiler, those inferred subclasses are explicitly included in the PermittedSubclasses
attribute.
The class
file of a permitted subclass carries no new attributes.
When the JVM attempts to define a class whose superclass or superinterface has a PermittedSubclasses
attribute, the class being defined must be named by the attribute. Otherwise, an IncompatibleClassChangeError
is thrown.