When should you choose to make a class or method `final` as a design decision, and what are the trade-offs?
answer
- decide: extension point vs leaf
- Effective Java: design for inheritance or prohibit it
- final defends against fragile base class
- value/immutable/utility/security types → final class
- trade-off: lost extensibility + mocking friction
basics
~20 sMake a class or method final when it isn't designed to be extended or changed — like value/immutable types or a security-critical method. The trade-off is lost flexibility: others can't subclass or override it, and some testing/mocking tools struggle with final.
solid answer
~50 sUse `final` to express "this isn't an extension point." Make value/immutable types (money, coordinates, DTOs) final because subclassing would only risk breaking their invariants. Make a method final when its behavior is part of a fixed contract — e.g. a template-method skeleton or a security check — that subclasses must not replace. The guiding principle from Effective Java is "design and document for inheritance, or else prohibit it": if you haven't carefully designed a class to be safely subclassed, defaulting to final prevents fragile-base-class bugs where a subclass breaks because it depended on the parent's internal call sequence. The trade-offs: you lose the ability to extend or override for legitimate future needs, and older mocking frameworks can't mock final classes/methods (modern ones with the inline mock-maker can). So final is a deliberate sealing choice, best applied to leaf types and stable contracts, and avoided on classes you genuinely intend as extension points.
go deeper
Knows final means 'can't extend/override' and that immutable/value types are good candidates.
Can list when to make a class vs a method final, names the testing/extensibility trade-off, and prefers composition over inheritance.
Explains the fragile base class problem, cites 'design for inheritance or prohibit it', and reasons about API-contract commitment of leaving a class open.
Defines org-wide policy (final-by-default, interfaces as the extension surface), weighs sealed vs final for closed hierarchies, and considers library evolution / binary compatibility of finalization decisions.
## The design question final answers Every class is implicitly one of two things: an **extension point** (meant to be subclassed) or a **leaf** (meant to be used as-is). `final` is how you *say which one* and have the compiler enforce it. Forgetting to decide is itself a decision — you leave the class open, and someone may extend it in ways you never anticipated or tested. ## The fragile base class problem Inheritance is tight coupling: a subclass depends not just on the parent's public API but on its *internal behavior* — which methods call which others, in what order. If a later version of the parent changes that internal call sequence, working subclasses can silently break. This is the **fragile base class problem**. Joshua Bloch's *Effective Java* states the rule: **"Design and document for inheritance, or else prohibit it."** Designing for inheritance means carefully documenting every self-use (which overridable methods the class calls internally) — expensive and constraining. Prohibiting it means marking the class `final` (or giving it only private constructors). For most classes, prohibiting is the safer default. ## When to make a CLASS final - **Value / immutable types:** money, coordinates, a parsed token, a config record. Subclassing could add mutable state or break `equals`/`hashCode`. (`record` types are implicitly final for this reason.) - **Utility classes** (only static methods) — no instances, nothing to extend. - **Security-sensitive types** whose behavior must be tamper-proof (`String`). - Any class you didn't *design* for inheritance and don't want to commit to supporting as a base class forever (every public extension point is an API contract). ## When to make a METHOD final - The method is a **template method**: a fixed algorithm skeleton that calls overridable hook methods. The skeleton is final; the hooks stay open. - The method enforces a **security or correctness invariant** (a permission check, an audit write) that no subclass may bypass. - A method whose behavior other (final) methods depend on, so overriding it would break the parent's own logic. Use a final *method* (rather than a final *class*) precisely when you *do* want the class extensible but need one behavior pinned. ## The trade-offs of choosing final 1. **Lost extensibility:** legitimate future subclassing/overriding becomes impossible without first un-finalizing (a source-compatible but intention-revealing change). Over-using final can make a library rigid. 2. **Testing/mocking:** older Mockito/EasyMock (proxy/subclass-based) cannot mock final classes or methods. Modern Mockito with the *inline* mock-maker can, but it's still friction; prefer designing to interfaces so you mock the interface, not the final implementation. 3. **Composition over inheritance:** final pushes consumers toward *composition* (wrapping/delegating) instead of inheritance — usually a good thing, but it does require the class to expose a usable public API. ## A practical default Many teams adopt: classes are final unless explicitly designed as extension points; interfaces and abstract classes are the sanctioned extension surface. This makes the inheritance contract intentional rather than accidental. ## How to derive the answer Ask: "Did I design and test this to be safely subclassed?" If no → make it final (prohibit). If yes but one method must stay fixed → final on that method. If the class genuinely is an extension point → leave it open and document the self-use. Then weigh the cost: lost extensibility and mocking friction versus protection from fragile-base-class breakage.
- Why are Java `record` types implicitly final?Records are designed as transparent, immutable data carriers whose identity is their components. Allowing subclassing would let a subclass add mutable state or override accessors, breaking the value-semantics and the equals/hashCode/toString the compiler generates. So the language makes records final automatically.
- How does `sealed` (Java 17+) relate to `final` as a design tool?`sealed` is a middle ground: instead of fully prohibiting inheritance (final) or fully allowing it (open), a sealed class permits only an explicit, named set of subclasses via `permits`. Each permitted subclass must itself be `final`, `sealed`, or `non-sealed`. It lets you design a closed, exhaustive hierarchy you fully control.
saying these in an interview costs you the question
- Defaulting everything to non-final 'just in case' someone wants to extend it
- Saying final has no downsides — it removes extensibility and complicates some mocking
- Confusing 'design for inheritance' with simply allowing inheritance — it requires documenting self-use
- Recommending inheritance over composition as the default extension mechanism