What are sealed classes in Java 17?

Viewed 17077

Today, I updated my Java version from 16 to 17, and I found that sealed classes is a new feature in it. I think it can be declared like this:

public sealed class Main permits AClass, AnotherClass {
}

But, what is the use of sealed classes in Java?

I also knew that it was a preview feature in jdk-15.

5 Answers

You can follow this link for examples.

In short sealed classes gives you the control of which models, classes etc. that can implement or extend that class/interface.

Example from the link:

public sealed interface Service permits Car, Truck {

    int getMaxServiceIntervalInMonths();

    default int getMaxDistanceBetweenServicesInKilometers() {
    return 100000;
  }
}

This interface only permits Car and Truck to implement it.

The JEP 409 explains it as

A sealed class or interface can be extended or implemented only by those classes and interfaces permitted to do so.

A more practical explanation is the following:

The situation in the past was:

  • You could not restrict an interface being extended by another interface
  • You could not constraint which classes where able to implement a specific interface.
  • You had to declare a class as final in order to not be extended by another class. This way no class could extend the declared final class. This was black or white approach.

The current situation with sealed keyword is:

  • You can now restrict an interface being extended by other interfaces and make a rule for only some specific interfaces which will be allowed to extend it.

    Example:

    public sealed interface MotherInterface permits ChildInterfacePermitted {}
    
    //Has to be declared either as sealed or non-sealed
    public non-sealed interface ChildInterfacePermitted extends MotherInterface {}  
    
    public interface AnotherChildInterface extends MotherInterface {} 
    //compiler error! It is not included in the permits of mother inteface
    
  • You can now create an interface and select only specific classes that are allowed to implement that interface. All other classes are not allowed to implement it.

    Example:

     public sealed interface MotherInterface permits ImplementationClass1 {} 
    
     //Has to be declared either as final or as sealed or as non-sealed
     public final class ImplementationClass1 implements MotherInterface {} 
    
     public class ImplementationClass2 implements MotherInterface {} 
     //compiler error! It is not included in the permits of mother inteface
    
  • You can now restrict a class being extended (same as before with final) but you can now allow some specific classes to extend it. So now you have more control as before the keyword final was absolute restricting every class from extending the declared final class

    Example:

    public sealed class MotherClass permits ChildClass1 {}
    
    //Has to be declared either as final or as sealed or as non-sealed
    public non-sealed class ChildClass1 extends MotherClass {} 
    
     public class ChildClass2 extends MotherClass {} 
     //compiler error! It is not included in the permits of MotherClass
    

Important notes:

  • The sealed class and its permitted subclasses must belong to the same module, and, if declared in an unnamed module, to the same package.

    Example:

    Let's say that we have the same unnamed module and the following packages

      -packageA
         -Implementationclass1.java
      -packageB
         -MotherClass.java
    

    or

       -root
          -MotherClass.java
          -packageA
             -Implementationclass1.java
    

    You will get the error Class is not allowed to extend sealed class from another package. So if you have an unnamed module all participating classes and interfaces for the sealed function must be placed exactly on the same package.

  • Every permitted subclass must directly extend the sealed class.

As per this documentation, Sealed classes and interfaces restrict which other classes or interfaces may extend or implement them. It is more of a declarative way to restrict the use of a superclass rather than using access modifiers.

In Java, a class can be final so no other classes can subclass it. If a class is not final, then it is open to all other classes to support code reusability. Doing so would raise data modeling concerns.

The below NumberSystem class is open to all classes, so any subclass can extend it. What if you want to restrict this NumberSystem to a fixed set of subclasses (Binary, Decimal, Octal, and HexaDecimal)?. It means you don’t want any other arbitrary class to extend this NumberSystem class.

class NumberSystem { ... }
final class Binary extends NumberSystem { ... }
final class Decimal extends NumberSystem { ... }
final class Octal extends NumberSystem { ... }
final class HexaDecimal extends NumberSystem { ... }

Using sealed class, you can achieve it by controlling the subclasses that can extend it and prevent any other arbitrary class from doing so.

All sealed java classes or interfaces must use permits keyword. For example:

Parent.class:

public sealed class Parent permits Child1, Child2 {
  void parentMethod() {
    System.out.println("from a sealed parent class ");
  }
}

Child1.java:

public final class Child1 extends Parent {
  public static void main(String[] args) {
    Child1 obj = new Child1();
    obj.parentMethod();
  }
}

Child2.java:

public final class Child2 extends Parent {
  public static void main(String[] args) {
    Child2 obj = new Child2();
    obj.parentMethod();
  }
}

Child3.java

public final class Child3 extends Parent {
  public static void main(String[] args) {
    Child3 obj = new Child3();
    obj.parentMethod();
  }
}

This Child3 class code will throw a compile-time error saying The type Child3 extending a sealed class Parent should be a permitted subtype of Parent (permits Child3, just like Child1 and Child2).

Sealed classes

A sealed class is a constraint that permits only given classes to implement it. These permitted classes must explicitly extend the sealed class and also have one of the sealed, non-sealed, or final modifiers. The feature is delivered as of java 17 (JEP 409) and was available as a preview longer before (Java 15).

sealed interface IdentificationDocument permits IdCard, Passport, DrivingLicence { }
final class IdCard implements IdentificationDocument { }
final class Passport implements IdentificationDocument { }
non-sealed class DrivingLicence implements IdentificationDocument { }
class InternationalDrivingPermit extends DrivingLicence {}

Usage with pattern matching

I find this new feature awesome in conjunction with pattern matching introduced as a preview as of Java 17 (JEP 406)!

The permitted class restriction assures all the subclasses are known in compile time. Using the switch expressions (JEP 361 as of Java 14) the compiler requires either to list all the permitted classes or use the default keyword for the remaining ones. Consider the following example using the classes above:

final String code = switch(identificationDocument) {
    case IdCard idCard -> "I";
    case Passport passport -> "P";
};

The compiler upon javac Application.java --enable-preview -source 17 results in an error:

Application.java:9: error: the switch expression does not cover all possible input values
                final String code = switch(identificationDocument) {
                                    ^
Note: Application.java uses preview features of Java SE 17.
Note: Recompile with -Xlint:preview for details.
1 error

Once all permitted classes or the default keyword are used, the compilation is successful:

final String code = switch(identificationDocument) {
    case IdCard idCard -> "I";
    case Passport passport -> "P";
    case DrivingLicence drivingLicence -> "D";
};
Related