HuntExams Academy logo
JavaAdvanced#fundamentals#java17

What is the difference between sealed classes and final classes (Java 17)?

Answer

A final class cannot be extended by anyone. A sealed class (Java 17, JEP 409) CAN be extended, but only by the classes you list in its permits clause \u2014 controlled inheritance rather than no inheritance. final: - final class Foo {} means zero subclasses; the hierarchy stops there. - Used for safety and immutability: String, Integer and LocalDate are all final. sealed: - public sealed interface Shape permits Circle, Square, Triangle {} - Every permitted subclass must itself be declared final, sealed, or non-sealed (which deliberately reopens that branch). - Permitted subclasses must live in the same module, or the same package for the unnamed module. If they are in the same source file, the permits clause can be omitted. - The permitted set is recorded in the class file (PermittedSubclasses), so both the compiler and the JVM enforce it. Why it exists: it gives Java algebraic data types. Because the compiler knows the complete set of subtypes, a switch over a sealed type is checked for exhaustiveness \u2014 no default branch needed, and adding a new subtype turns every incomplete switch into a compile error instead of a runtime surprise. Together with records and pattern matching for switch (final in Java 21), this is the idiomatic way to model closed domains: payment methods, expression trees, API result types. Interview one-liner: final says "nobody may extend me"; sealed says "only these named types may extend me, and the compiler will hold you to it". Use final for one immutable implementation, sealed for a fixed, known family of subtypes.

Example

// final: no subclass at all
public final class Money { /* ... */ }
// class Cash extends Money {}    // compile error

// sealed: a closed, known family
public sealed interface Shape permits Circle, Square, Triangle {}

public record Circle(double radius)           implements Shape {}
public record Square(double side)             implements Shape {}
public record Triangle(double base, double h) implements Shape {}

// Exhaustive switch, no default needed (pattern matching, Java 21)
static double area(Shape s) {
    return switch (s) {
        case Circle c   -> Math.PI * c.radius() * c.radius();
        case Square q   -> q.side() * q.side();
        case Triangle t -> 0.5 * t.base() * t.h();
    };
}
// Add a Hexagon to permits and this switch stops compiling
// until you handle the new case.

// Mixed subclass modifiers
public sealed class Payment permits Card, Upi, Wallet {}
public final      class Card   extends Payment {}
public sealed     class Upi    extends Payment permits Gpay {}
public non-sealed class Wallet extends Payment {}   // reopened on purpose
public final      class Gpay   extends Upi {}

Detailed explanation

In Java 17, final and sealed control inheritance at different levels. A final class cannot have any subclasses: final class Money {} prevents another class from extending Money. A sealed class allows inheritance, but restricts its direct subclasses to an approved set. Sealed classes and interfaces became a standard feature in Java 17 through JEP 409. The central distinction is therefore no inheritance versus controlled inheritance, not immutable versus mutable objects.

For example, sealed interface Shape permits Circle, Square {} allows only Circle and Square to directly implement Shape. Each permitted type must directly extend or implement the sealed type. A permitted subclass must be final, sealed, or non-sealed: final closes that branch, sealed restricts its next level, and non-sealed allows unrestricted further inheritance. A permitted subinterface uses sealed or non-sealed because interfaces cannot be final. Records work naturally as leaf implementations because they are implicitly final. For instance, record Circle(double radius) implements Shape {} requires no explicit final modifier.

Permitted types must belong to the same named module as the sealed parent; when using the unnamed module, they must belong to the same package. The permits clause may be omitted when the compiler can infer the direct subclasses from declarations in the same compilation unit. Under the hood, a sealed type’s class file contains a PermittedSubclasses attribute. Both compilation and JVM class loading enforce the restriction, so sealing is more than a source-code convention. It controls direct inheritance; a non-sealed branch can still introduce arbitrarily many indirect subtypes.

Use final when subclassing should never be supported, and sealed when modelling a controlled family such as expression nodes or payment types. Final prevents subclasses from altering behaviour, but does not itself make fields or referenced objects immutable. Sealed hierarchies also support exhaustive pattern switches, although pattern matching for switch is preview functionality in Java 17 and becomes final in Java 21. With an exhaustive subtype switch and no catch-all branch, adding an uncovered permitted subtype causes a compilation error when that switch is recompiled. Interviewers typically look for these boundaries, especially the modifier rules, module restrictions, and Java-version distinction.

Key points to remember

  • final prohibits all subclasses; sealed restricts direct subclasses to permitted types.
  • Permitted subclasses must be final, sealed, or non-sealed; records are implicitly final.
  • Permitted types share the parent’s named module, or its package in the unnamed module.
  • The compiler and JVM enforce sealing using the PermittedSubclasses class-file attribute.
  • Sealed types are standard in Java 17; pattern matching for switch is standard only from Java 21.

Common mistakes candidates make

  • Claiming that a final class is automatically immutable; it may still contain mutable state.
  • Saying every descendant of a sealed class is known, overlooking the open-ended descendants of a non-sealed branch.
  • Presenting pattern switches as a non-preview Java 17 feature or claiming hierarchy changes automatically invalidate already compiled clients.

Likely follow-up questions

Can a class be both sealed and abstract?

Yes: abstract sealed class Expr permits Literal, Add {} is valid. Abstract prevents direct instantiation, while sealed independently restricts direct subclasses.

When should a permitted subclass be non-sealed?

Use non-sealed when one approved branch should remain an extension point. Any eligible class can extend that branch without being listed in the original sealed parent’s permits clause.

Does adding a permitted subtype break existing compiled switches?

It does not automatically trigger compilation errors in already compiled clients. On recompilation, a previously exhaustive switch without a catch-all fails if it leaves the new subtype uncovered; in Java 21, encountering an uncovered subtype in an older compiled exhaustive pattern switch can throw MatchException.

Interview tip: Start with “final forbids inheritance; sealed controls it,” then demonstrate the three permitted subclass modifiers and mention the Java 17 switch-preview caveat.

Related Interview Questions

1
JavaIntermediate#fundamentals#java14

What is the difference between switch statement and switch expression (Java 14+)?

Open
2
JavaBeginner#strings#java15

What is text block syntax in Java (Java 15+)?

Open
3
JavaIntermediate#strings

What is the String pool (intern pool)?

Open