Sealed Classes & Interfaces practice questions

From OCP Java SE 21 (1Z0-830) · 14 questions on this topic

Sealed Classes & Interfaces practice questions from OCP Java SE 21 (1Z0-830). This pack has 14 questions tagged Sealed Classes & Interfaces, drawn from its timed mock exams. 8 of them are worked through in full below — the question, every option, why each is right or wrong, and the explanation.

Worked examples for Sealed Classes & Interfaces

  1. Question 1

    What does the following program print? ```java public class Main { sealed interface Result permits Ok, Err {} record Ok(String value) implements Result {} non-sealed interface Err extends Result {} record BadInput(String message) implements Err {} public static void main(String[] args) { Result r = new Ok("done"); String out = switch (r) { case Ok ok -> ok.value(); case Err err -> "error"; }; System.out.println(out); } } ```

    1. A. doneCorrect answer

      `r` holds an `Ok` instance whose record component `value` is `"done"`. The switch matches `case Ok ok`, evaluates `ok.value()`, and assigns `"done"` to `out`, which is then printed. The code compiles without error: `Err` is a lawful `non-sealed` permitted subtype of the sealed `Result`, `BadInput` lawfully implements the `non-sealed` `Err` without appearing in any `permits` clause, and the switch is exhaustive because all direct permitted subtypes of `Result` are covered.

    2. B. Compilation fails — `BadInput` does not appear in any `permits` clause

      `BadInput` implements `Err`, which is declared `non-sealed`. A `non-sealed` permitted subtype intentionally reopens the hierarchy: any class or interface may extend or implement it without being listed in any `permits` clause. The compiler imposes no such requirement on `BadInput` (JEP 409).

    3. C. Compilation fails — the switch expression is not exhaustive because `Err` is `non-sealed`

      Exhaustiveness is evaluated over a sealed type's *direct* permitted subtypes, not over every concrete implementation that may exist at runtime. `Result` permits exactly `Ok` and `Err`; the switch covers both. A `case Err err` arm catches every value whose runtime type implements `Err`, so the switch is exhaustive regardless of how many unknown classes may implement the `non-sealed` `Err` (JLS §14.28.2).

    4. D. error

      `r` is constructed as `new Ok("done")`, an instance of `Ok`, not of any `Err` implementation. The switch matches the first arm (`case Ok ok`), not the second, so the string literal `"error"` is never evaluated.

    Explanation

    A `non-sealed` permitted subtype breaks out of the closed hierarchy on purpose: any class or interface may extend or implement it freely, with no obligation to appear in a `permits` clause, so `BadInput implements Err` is valid and raises no compilation error. Exhaustiveness of a switch expression over a sealed type is satisfied by covering every *direct* permitted subtype; a `case Err err` arm catches all implementations of `Err` at runtime, making the switch exhaustive even though `Err` is open for arbitrary extension. The runtime value `new Ok("done")` is not an `Err`, so the second arm is bypassed entirely and the program prints `done` (JEP 409; JLS §14.28.2).

  2. Question 2

    What is the output of the following program? ```java public class Main { sealed interface Notification permits Email, Push {} record Email(String to) implements Notification {} non-sealed interface Push extends Notification {} record AppPush(String token) implements Push {} record SmsPush(String phone) implements Push {} static String describe(Notification n) { return switch (n) { case Email e -> "email"; case Push p -> "push"; }; } public static void main(String[] args) { System.out.println(describe(new AppPush("abc")) + " " + describe(new Email("[email protected]"))); } } ```

    1. A. push emailCorrect answer

      `AppPush` implements the `non-sealed` interface `Push`, which is one of `Notification`'s two permitted subtypes. Calling `describe(new AppPush("abc"))`: `AppPush` is not an `Email`, but it is a `Push`, so `case Push p` matches and returns `"push"`. Calling `describe(new Email("[email protected]"))`: `Email` matches `case Email e` and returns `"email"`. Concatenated with a space the output is `push email`.

    2. B. Compilation fails

      The switch expression is exhaustive over `Notification`, which is sealed with exactly two permitted subtypes: `Email` and `Push`. The cases handle both, so the compiler accepts the expression without a `default` arm. That `Push` is `non-sealed` does not break exhaustiveness — `case Push p` is a type-pattern arm that matches every runtime subtype of `Push` however many implementations exist, and `AppPush` and `SmsPush` may implement it freely precisely because `Push` carries `non-sealed` (JEP 409).

    3. C. email push

      This reverses the order. The first argument is `new AppPush("abc")`, which matches `case Push p` and produces `"push"`. The second argument, `new Email("[email protected]")`, produces `"email"`. Evaluating the concatenation left to right, `"push"` comes first.

    4. D. Throws MatchException at runtime

      `MatchException` is thrown by an exhaustive switch only when no arm matches at runtime — a situation that can arise if the sealed hierarchy is widened after the switch was compiled. Here the switch covers all current and future subtypes of `Notification`: every `Email` matches `case Email e`, and every `Push` implementation — including `AppPush` — matches `case Push p`. No unmatched value can exist, so no exception is thrown.

    Explanation

    A `non-sealed` permitted subtype reopens the inheritance hierarchy at that node: any class may implement `Push` freely without appearing in any `permits` clause, so `AppPush` and `SmsPush` are valid. This does not break exhaustiveness for a switch over the sealed parent type. A type-pattern arm `case Push p` matches every value whose runtime type is `Push` or a subtype of `Push`, so the compiler considers `Notification` fully covered once both `Email` and `Push` are handled — no `default` is required. At runtime an `AppPush` instance is-a `Push` and resolves to the second arm; an `Email` instance resolves to the first.

  3. Question 3

    `Shape` is a sealed interface whose sole permitted subtype is the record `Circle`. `Blob` is an ordinary, unrelated class — it is not final and does not implement `Shape`. What is the result? ```java public class Main { sealed interface Shape permits Circle {} record Circle(int r) implements Shape {} static class Blob { int size = 1; } public static void main(String[] args) { Blob b = new Blob(); System.out.println(b instanceof Shape); } } ```

    1. A. Compilation fails: a sealed interface may not appear as the type in an `instanceof` test; only its permitted subtypes may

      Invents a rule. Testing against a sealed type is normal (Object o = new Circle(1); o instanceof Shape compiles and is true). What fails here is this specific incompatible pair of types.

    2. B. Compilation fails: incompatible types — `Blob` cannot be converted to `Shape`Correct answer

      Sealing makes Shape's only permitted subtype Circle, which is not a subtype of Blob, so no object can be both; the reference conversion is provably impossible and javac rejects the instanceof as incompatible types.

    3. C. It prints `false` — no `Blob` implements `Shape`, so the test is simply false at run time

      This is what happens if you delete the sealed/permits clause: the identical program then compiles and prints false. It is the misconception under test, that a failed instanceof is always a runtime false even when the target is sealed.

    4. D. Compilation fails: `Blob` must be declared `final` before it can be tested against a sealed type

      Gets the direction backwards. Marking Blob final would not help; finality is one of the ways the compiler proves impossibility, so the error would remain.

    Explanation

    Trace: `instanceof` is a compile-time error whenever the cast it implies is provably impossible. For an ordinary interface the compiler must stay optimistic — someone could later write `class Sub extends Blob implements Shape`, so a `Blob` reference might one day hold a `Shape`, and the test compiles. Sealing removes that possibility: `Shape` permits exactly one type, the record `Circle`, and `Circle` is not a subtype of `Blob`. The compiler therefore knows no object can ever be both, the reference conversion is impossible, and javac rejects the line with `error: incompatible types: Blob cannot be converted to Shape`. Sealing genuinely changes the type system, not just the set of classes you may write. Why the others are wrong: `It prints `false` — no `Blob` implements `Shape`...` is what happens if you delete the `sealed`/`permits` clause: the identical program then compiles and prints `false`. It is the right answer to the wrong question, and it is the misconception being tested — that a failed `instanceof` is always a run-time `false` rather than a compile-time impossibility. `Compilation fails: a sealed interface may not appear as the type in an `instanceof` test...` invents a rule. Testing against a sealed type is completely normal — `Object o = new Circle(1); o instanceof Shape` compiles and is `true`. What fails here is this specific pair of types. `Compilation fails: `Blob` must be declared `final`...` gets the direction backwards. Marking `Blob` final would not help; if anything, finality is one of the ways the compiler proves impossibility, and the error would remain. Exam tip: with a sealed target type, ask whether *any* permitted subtype could be an instance of the operand's type. If none can, the `instanceof` — and the corresponding cast — is a compile error, not a run-time `false`. The same reduction is what lets an exhaustive switch over a sealed type omit `default`.

  4. Question 4

    Which two statements about sealed types are correct? (Choose two.)

    1. A. An enum class may implement a sealed interfaceCorrect answer

      Correct. Enums fit sealed hierarchies naturally: an enum may implement a sealed interface and needs no modifier because it is implicitly final (or implicitly sealed when its constants have class bodies).

    2. B. A sealed class cannot be declared abstract

      Wrong. `abstract sealed class Shape permits ... {}` is legal and idiomatic; sealed restricts WHO can extend while abstract restricts instantiation, and the two compose.

    3. C. An anonymous class cannot extend a sealed classCorrect answer

      Correct. Only named, permitted subclasses may extend a sealed class; an anonymous class has no canonical name and can never appear in or be checked against a permits clause.

    4. D. The non-sealed modifier may be applied to a class that has no sealed direct supertype

      Wrong. It is a compile-time error to use `non-sealed` on a class or interface with no sealed direct supertype; the modifier only makes sense as a response to a seal — there must be a seal to un-seal.

    Explanation

    Enums fit sealed hierarchies naturally: an enum may implement a sealed interface with no modifier because it is implicitly final (or implicitly sealed when its constants carry class bodies). Anonymous classes, by contrast, can never extend a sealed class, since only named permitted subclasses can appear in or be checked against a permits clause. Sealing composes freely with `abstract` — an abstract sealed class is legal — while `non-sealed` is only meaningful when there is a sealed direct supertype to re-open.

  5. Question 5

    With a sealed interface and a switch covering every permitted subtype, why can the default branch be omitted?

    1. A. A switch on a sealed type never requires exhaustiveness

      Wrong. Exhaustiveness IS required; the switch compiles without `default` only because covering the whole sealed hierarchy proves it.

    2. B. Only enums allow omitting default; sealed types always require it

      Wrong. Sealed types get the same treatment as enums: cover every permitted subtype and `default` may be dropped.

    3. C. The compiler can prove the switch is exhaustive from the sealed hierarchy, so no default is neededCorrect answer

      Correct. A sealed type's permitted subtypes form a closed set the compiler knows completely, so a case for every permitted subtype lets the compiler PROVE exhaustiveness and `default` becomes unnecessary (JEP 441).

    4. D. Sealed interfaces are implicitly handled by an injected default

      Wrong. The language injects no `default` into your source semantics. (The compiler does synthesize a hidden throwing branch for the binary-compatibility case where a new subtype appears later, but that is a runtime safety net, not the reason `default` may be omitted.)

    Explanation

    A sealed type's permitted subtypes form a closed set the compiler knows completely, exactly like the constants of an enum. When a switch has a case for every permitted subtype, the compiler can therefore PROVE exhaustiveness — the property a switch expression requires — so `default` is unnecessary. Exhaustiveness is still genuinely required here; it is satisfied by the coverage, not waived. Adding a new subtype later turns this into a compile error at the next recompile, which is the intended feature.

  6. Question 6

    What is the output of the following program? ```java sealed interface Result permits Ok, Err {} record Ok(int value) implements Result {} non-sealed class Err implements Result { String message() { return "error"; } } class TimeoutErr extends Err { @Override String message() { return "timeout"; } } public class Main { public static void main(String[] args) { Result r = new TimeoutErr(); String out = switch (r) { case Ok ok -> "ok:" + ok.value(); case Err err -> err.message(); }; System.out.println(out); } } ```

    1. A. error

      Assumes the declared type of the case-binding variable (Err) governs method selection — i.e., static dispatch on the reference type. Java instance-method invocation is always dynamic: the runtime type's virtual method table is consulted, so TimeoutErr.message() is called rather than Err.message().

    2. B. timeoutCorrect answer

      TimeoutErr extends the non-sealed Err, so it is an instanceof Err (and therefore a valid Result). The case Err err arm matches because TimeoutErr satisfies instanceof Err, binding err to the TimeoutErr object. Method invocation is virtual: err.message() dispatches to TimeoutErr.message(), which returns "timeout".

    3. C. Compilation fails

      The switch is exhaustive and compiles cleanly. case Ok ok covers Ok (which is a record, hence implicitly final), and case Err err covers Err and all of its subclasses — including TimeoutErr. Because Result is sealed with exactly two permitted subtypes, these two arms together account for every possible Result value; no default arm is required (JEP 409; JEP 441).

    4. D. Throws `MatchException` at runtime

      Assumes case Err err matches only direct Err instances and silently bypasses TimeoutErr. A type pattern succeeds whenever the runtime value is an instanceof the named type; TimeoutErr passes that test because it extends Err. Every Result value is covered by one of the two arms, so MatchException is never thrown.

    Explanation

    A permitted subtype declared non-sealed reopens its branch of the hierarchy: any class may extend it without appearing in the original permits clause, and those further subclasses are still instances of the non-sealed type. A type pattern for a non-sealed type in a switch arm therefore covers the entire subclass tree below it — here, case Err err matches TimeoutErr just as it matches Err itself. The two arms are jointly exhaustive over the sealed Result hierarchy, satisfying the Java 21 compiler without a default, and virtual dispatch ensures the most-specific override is invoked on the bound reference.

  7. Question 7

    A record class implements a sealed interface. Which modifier, if any, must the record declare to satisfy the sealed-hierarchy rules?

    1. A. final, because every permitted subtype must carry an explicit final, sealed, or non-sealed modifier

      Wrong. The requirement is that the finality property holds, not that the keyword is written; a record is final whether or not you write it (a redundant `final` is allowed but never required).

    2. B. None — a record is implicitly final, which already satisfies the requirementCorrect answer

      Correct. A record class is implicitly final (JLS §8.10), so it already satisfies the permitted-subtype rule with no modifier at all; `record Circle(double r) implements Shape {}` compiles as written.

    3. C. non-sealed, because records are otherwise open to extension

      Wrong. Records cannot be extended at all, so `non-sealed` would contradict their nature and is illegal on a record.

    4. D. sealed, with a permits clause naming the record itself

      Wrong. A record cannot be sealed (nothing can extend it), and a type never permits itself.

    Explanation

    Every permitted subtype of a sealed type must be final, sealed, or non-sealed — but a record class is implicitly final (JLS §8.10), so it already meets that rule without writing any modifier. Because a record can never be extended, the modifiers that assume further extension (`non-sealed`, `sealed`) are meaningless or illegal on it. So the record implements the sealed interface exactly as written, with no extra modifier needed.

  8. Question 8

    A sealed class permits one final and one non-sealed subclass; the non-sealed subclass is then extended further, and a switch with no default arm covers the two permitted subtypes. What happens? ```java public class Main { abstract static sealed class Node permits Leaf, Branch {} static final class Leaf extends Node { final int v; Leaf(int v) { this.v = v; } } static non-sealed class Branch extends Node {} static class Fork extends Branch {} static String kind(Node n) { return switch (n) { case Leaf l -> "leaf" + l.v; case Branch b -> "branch"; }; } public static void main(String[] args) { System.out.println(kind(new Fork()) + " " + kind(new Leaf(7))); } } ```

    1. A. Throws MatchException

      Assumes the indirect subclass matches no arm; it is a Branch and matches the Branch case, and the permitted list restricts only direct subclasses, not indirect ones matching a supertype pattern.

    2. B. leaf7 branch

      Simply reverses the call order; the indirect subclass is evaluated and printed first, giving "branch" before "leaf7".

    3. C. branch leaf7Correct answer

      The non-sealed Branch lets the further subclass extend it, that subclass matches the Branch case ("branch"), the Leaf matches the Leaf case ("leaf7"), and covering both permitted subtypes makes the switch exhaustive.

    4. D. Compilation fails

      Covering both permitted subtypes of the sealed type makes the switch exhaustive, so no default is required and it compiles.

    Explanation

    A non-sealed subclass re-opens a sealed hierarchy, so further subclasses of it are permitted and still match a case for their supertype. A switch that covers every permitted direct subtype of a sealed type is exhaustive without a default clause, and an indirect subclass is simply matched by whichever supertype pattern applies.

Practise all 14 Sealed Classes & Interfaces questions

OCP Java SE 21 has the full set, inside timed mock exams that mirror real exam conditions — every question with a worked explanation.

Open OCP Java SE 21

Other topics in this pack