Skip to main content

Command Palette

Search for a command to run...

Sealed Classes in Java

Updated
•6 min read•View as Markdown
Sealed Classes in Java
M

Oracle‬‭ Certified‬‭ Java‬‭ Developer‬‭ with‬‭ 7+ years‬‭ of‬‭ experience‬‭ specializing‬‭ in‬‭ backend‬‭ development,‬‭ microservices‬ architecture,‬‭ and‬‭ cloud-based‬‭ solutions.‬‭ Proven‬‭ expertise‬‭ in‬‭ designing‬‭ scalable‬‭ systems,‬‭ optimizing‬‭ performance,‬‭ and‬ mentoring‬‭ teams‬‭ to‬‭ enhance‬‭ productivity.‬‭ Passionate‬‭ about‬‭ building‬‭ high-performance‬‭ applications‬‭ using‬‭ Java,‬‭ Spring‬ Boot, Kafka, and cloud technologies (AWS/GCP)

Sealed classes are one of the biggest “modern Java” features that came after Java 8, finalized in Java 17 (LTS). They give you more control over inheritance and make class hierarchies safer and easier to reason about.


1. The problem they solve

In Java 8, if you wrote an abstract class or interface, any class in the world could extend/implement it (unless you made the constructor/package-private). That meant:

  • You couldn’t always predict or restrict the hierarchy.

  • Exhaustive switch or instanceof checks weren’t possible (because subclasses could be added anywhere).

  • This made reasoning, pattern matching, and security harder.


2. What sealed classes do

With a sealed class (or interface), you explicitly list which classes are allowed to extend/implement it.

Syntax:

public sealed class Shape 
    permits Circle, Rectangle, Square { }

public final class Circle extends Shape { }
public non-sealed class Rectangle extends Shape { }
public sealed class Square extends Shape permits ColoredSquare { }

Keywords:

  • sealed → restricts which classes can extend it (must be in same package or module).

  • permits → lists the allowed subclasses.

  • final → subclass can’t be extended further.

  • sealed → subclass itself continues the sealing, listing its own permitted subclasses.

  • non-sealed → subclass lifts the restriction, allowing free extension again.


3. Non-Sealed Classes in Java

When you define a sealed class or interface (Java 17+), you explicitly list which classes are allowed to extend/implement it.
But not every subclass needs to remain sealed or final.

That’s where non-sealed comes in:

  • It removes the restriction and allows any other class to extend it.

  • Useful when you want to control the top-level hierarchy but leave some branches open for extensibility.

Example:-

Sealed interface

sealed interface Shape permits Circle, Rectangle, Polygon { }

Sealed and final implementations

final class Circle implements Shape { }
final class Rectangle implements Shape { }

Non-sealed implementation

non-sealed class Polygon extends Shape { }

class Triangle extends Polygon { }
class Hexagon extends Polygon { }

Here:

  • Shape is sealed → only Circle, Rectangle, and Polygon are allowed to implement it.

  • Circle and Rectangle are final → no further subclassing.

  • Polygon is non-sealed → other classes are free to extend it, even outside the original permits list.


4. Do subclasses of a sealed class need final / sealed / non-sealed?

Yes, it’s mandatory.
Every direct subclass of a sealed class (or sealed interface) must explicitly declare one of the following modifiers:

  1. final → no further subclassing allowed.

  2. sealed → subclass is also sealed, and must declare its own permits list.

  3. non-sealed → subclass removes restrictions, allowing free subclassing again.

Why this rule?

  • Ensures clarity of hierarchy → the compiler knows if the subclass hierarchy is closed, open, or final.

  • Makes exhaustive pattern matching possible in switch.

  • Prevents ambiguity (you always know whether further extension is possible).

If you omit final, sealed, or non-sealed in a permitted subclass, the compiler throws an error.


5. Class is not allowed to extend sealed class from another package why is it so?

This is actually a Java language rule, not something specific to your project.

🔹 Why you see this error

In Java, when you declare a sealed interface or class, you must list its permitted subclasses with permits. But there’s a scope restriction:

  • If your code is not using modules (module-info.java):
    ➝ All permitted subclasses must be in the same package as the sealed type.

  • If you are using modules (Java 9+ with module-info.java):
    ➝ All permitted subclasses must be in the same module (not necessarily the same package).

🔹 How to fix

You have two options:

  1. Keep everything in the same package
    Move RestEasyProvider into com.demo.identity.project.services so it’s in the same package as JaxRsService.

  2. Use modules
    If you have a module-info.java, you can relax the rule by making both the interface and its implementations part of the same module. Then the implementations can live in different packages.

    Example in module-info.java:

     module com.demo.identity.project {
         exports com.demo.identity.project.services;
         exports com.demo.identity.project.providers;
     }
    

    This way, both JaxRsService and RestEasyProvider are in the same module, and sealing works across packages.

    🔹 Why Java enforces this

It’s about enforceability and maintainability:

  • The compiler must know all permitted subclasses at compile time.

  • Without modules, package-level sealing ensures all permitted types are in the same compilation unit.

  • With modules, sealing extends to the module boundary.

Note:-

I have create a blank module-info.java class. And the error is gone. Why is it so?

That makes sense — the reason the error disappeared once you introduced a module-info.java is tied to how the Java Platform Module System (JPMS) treats sealed classes.

Why the error existed before

  • When your code was on the classpath (no modules), the JLS rule says:
    👉 A sealed class can only be extended by classes in the same package.

  • Since your base class and subclass were in different packages (services vs providers), the compiler rejected it.


What changed after adding module-info.java

By introducing module-info.java, you converted your code into a named module.
Once classes belong to a named module:

  • The rule for sealed classes changes:
    👉 A sealed class can be extended by permitted subclasses that are in the same module (they no longer need to be in the same package).

  • In your sealed class, you explicitly listed the subclass in the permits clause, and both classes are now in the same module (com.demo.identity.project).

  • The compiler now recognizes this relationship as valid → the error is gone.


Summary

  • Classpath mode (no modules):
    Sealed base + subclass must be in the same package.

  • Module mode (with module-info.java):
    Sealed base + subclass may be in different packages, as long as they are in the same module.

That’s exactly why declaring module-info.java fixed your issue: it lifted the restriction from “same package” → “same module.” ✅


5. Why it matters

  • Safer modeling: You can encode business rules in the type system. Example: A PaymentMethod sealed to only Card, UPI, Wallet.

  • Exhaustive checks: With pattern matching for switch (Java 21), the compiler can verify you’ve covered all cases of a sealed hierarchy.

  • Better readability & maintainability: Readers instantly know the closed set of implementations.


6. Example with pattern matching

sealed interface PaymentMethod permits Card, UPI, Wallet { }

record Card(String number) implements PaymentMethod { }
record UPI(String id) implements PaymentMethod { }
record Wallet(String provider) implements PaymentMethod { }

public String process(PaymentMethod p) {
    return switch (p) {
        case Card c   -> "Processing card " + c.number();
        case UPI u    -> "Processing UPI " + u.id();
        case Wallet w -> "Processing wallet " + w.provider();
    }; // compiler ensures this switch is exhaustive
}

7. Where you’ll use them

  • Modeling fixed domain hierarchies (payment types, shapes, states in a workflow, commands/events).

  • Working with pattern matching (switch + records).

  • Replacing “enums with data”: instead of an enum plus multiple unrelated data holders, you can model each as a sealed subtype.