skip to content

Every direct subtype of a sealed class must itself declare one of three modifiers - which three, and what does each mean?

level: middleimportance: must knowfreq 68%

answer

  1. final / sealed / non-sealed - pick exactly one
  2. final = chain stops
  3. sealed = continue closed, needs its own permits
  4. non-sealed = branch reopens to everyone
  5. records are implicitly final

basics

~10 s

Each permitted subclass must be final (no further subclasses), sealed (continues the closed hierarchy with its own permits list), or non-sealed (reopens that branch so anyone can extend it).

solid answer

~40 s

A permitted subtype of a sealed class or interface is required by the compiler to explicitly choose how *it* continues the hierarchy, using exactly one of three modifiers. final means the subtype closes the chain - nothing can extend it further. sealed means the subtype is itself a sealed type and must declare its own permits list, continuing the controlled hierarchy one level deeper. non-sealed deliberately reopens that branch: it removes the sealing constraint, so any class may once again extend it without being listed anywhere. This rule exists so the 'closedness' of a sealed hierarchy is explicit at every level - a reader can see exactly where the closed set ends and where (if anywhere) it becomes open again. Records are implicitly final, so a record permitted subtype satisfies the final requirement automatically.

code

java · 16 lines
java
public sealed interface Shape
        permits Circle, Polygon, CustomShape {}

// 1) final: chain stops here
public record Circle(double radius) implements Shape {} // implicitly final

// 2) sealed: continue the closed hierarchy with its own permits
public sealed interface Polygon extends Shape
        permits Triangle, Square {}
public record Triangle(double a, double b, double c) implements Polygon {}
public record Square(double side) implements Polygon {}

// 3) non-sealed: reopen this branch to anyone
public non-sealed interface CustomShape extends Shape {}
// now ANY class may implement CustomShape, e.g. a third party:
class MyWeirdShape implements CustomShape {}

go deeper

for a junior

Can name the three modifiers (final, sealed, non-sealed) and that one is mandatory on each permitted subtype.

for a middle

Explains what each modifier means for further extension and knows records are implicitly final so they satisfy the final case.

for a senior

Articulates why the explicit-choice rule exists (closedness must be visible at every level) and uses non-sealed deliberately as an escape hatch in API design.

for a principal

Evaluates the maintainability and contract implications of each branch choice, e.g. when non-sealed is a code smell that undermines exhaustiveness versus a deliberate extension point for clients.

## The rule When a class or interface is `sealed`, the compiler does **not** let its permitted subtypes be 'plain'. **Every direct permitted subtype must explicitly declare one of exactly three modifiers:** ### 1. `final` The subtype **closes the chain**: nothing may extend it further. The hierarchy stops here. ```java public sealed interface Shape permits Circle, Square {} public final class Circle implements Shape {} ``` ### 2. `sealed` The subtype is itself a sealed type and **must declare its own `permits`** (or rely on same-file inference). The closed hierarchy **continues one level deeper**. ```java public sealed interface Shape permits Polygon, Circle {} public sealed interface Polygon extends Shape permits Triangle, Square {} public final class Triangle implements Polygon {} public final class Square implements Polygon {} public final class Circle implements Shape {} ``` ### 3. `non-sealed` The subtype **deliberately reopens** the branch. It drops the sealing constraint, so **any class may extend it** again, with no need to appear in any `permits` list. ```java public sealed interface Shape permits Circle, CustomShape {} public non-sealed interface CustomShape extends Shape {} // anyone may implement CustomShape now ``` ## Why the rule exists The whole value of sealing is a **known, closed set**. If a permitted subtype could be silently re-extended by anyone, the closedness would leak away invisibly. By **forcing an explicit choice** at every level, Java makes the boundary of the closed set **visible in the source**: a reader sees exactly where the set is final, where it keeps narrowing (`sealed`), and where it intentionally opens back up (`non-sealed`). ## Important details - **Exactly one** of the three is required on each direct permitted subtype - you cannot omit it, and you cannot combine `final` with `non-sealed` (they are contradictory). - **Records are implicitly `final`.** So a `record` that is a permitted subtype already satisfies the `final` requirement - you do **not** add (and cannot add) another modifier. This is why sealed-interface + record combinations are so common. - `non-sealed` is the only Java keyword with a hyphen. - The choice is per-direct-subtype; different branches can choose differently (one `final`, one `sealed`, one `non-sealed`). ## Terms defined - **Permitted subtype:** a type listed in (or inferred for) the `permits` clause. - **Closing the chain:** making further extension impossible (`final`). - **Reopening:** removing the sealing constraint so arbitrary extension is allowed again (`non-sealed`).

  • If a permitted subtype is a record, which modifier do you write?
    None - records are implicitly final, which already satisfies the requirement, and you cannot add another modifier.
  • What does non-sealed accomplish?
    It reopens that branch of the hierarchy: any class may extend the non-sealed type without being listed in any permits clause, removing the closed-set guarantee for that subtree.

saying these in an interview costs you the question

  • Saying a permitted subtype can just be a plain class with no modifier - it must explicitly be final, sealed, or non-sealed
  • Thinking non-sealed still requires listing extenders - it explicitly removes that constraint
  • Adding final to a record subtype (records are already implicitly final)
  • Believing you can mark a subtype both final and non-sealed

context