skip to content

Why is it dangerous to call an overridable (non-final, non-private) method from inside a constructor? What can go wrong?

level: seniorimportance: should knowfreq 55%

answer

  1. Parent ctor runs while child fields are still default (0/null)
  2. Dynamic dispatch is live during construction → override runs early
  3. Symptom: NPE / zero from a half-built object
  4. Call only private/static/final from a constructor
  5. Same hazard in clone() and readObject()

basics

~20 s

If a parent constructor calls a method the child overrides, the child's version runs while the child object isn't finished being built yet. The child's fields are still at their defaults (0/null), so the override can misbehave or crash.

solid answer

~50 s

Java uses dynamic dispatch: an overridden method call always resolves to the most-derived implementation, even when the call originates in a superclass constructor. The problem is timing. Constructors run parent-first, so when the parent constructor runs, the child's own field initializers and constructor body have *not* run yet — the child's fields still hold their default values (0, false, null). If the parent constructor calls an overridable method that the child overrides, the child's override executes against this half-built object and sees those uninitialized fields. The classic symptom is a NullPointerException or wrong/zero values from an override that assumed its fields were set. The fix is to never call overridable methods from a constructor; call only private, static, or final methods (which can't be overridden), or move the logic out of construction (e.g. a factory or init step run after the object is fully built).

code

java · 18 lines
java
class Base {
    Base() {
        // Dynamic dispatch: this calls Derived.greet() during construction
        System.out.println(greet());
    }
    String greet() { return "hello from Base"; }
}

class Derived extends Base {
    private final String name = "world"; // initializer runs AFTER super()
    @Override
    String greet() {
        // 'name' is still null here when called from Base's constructor
        return "hello " + name.toUpperCase(); // NullPointerException!
    }
}

// new Derived();  -> throws NPE: Base() -> Derived.greet() -> name is null

go deeper

for a junior

Recognizes that calling a method from a constructor can be risky and that fields may not be set yet; may not yet explain dispatch.

for a middle

Explains that dynamic dispatch picks the subclass override and that the subclass fields are still at defaults during super-construction, producing NPEs.

for a senior

Articulates the precise interaction of parent-first construction and live dynamic dispatch, gives the safe alternatives (private/static/final, post-construction init), and avoids the 'final fixes it' trap.

for a principal

Frames it as the Effective Java inheritance-design rule, extends the hazard to clone()/readObject(), contrasts Java's uniform dispatch with C++/C#'s constructor-type resolution, and reasons about API design to make classes safe to subclass or final by default.

## Terms - *Overriding* is when a subclass provides its own implementation of a method declared in a superclass, with the same signature. - *Dynamic dispatch* (a.k.a. virtual call, late binding) is Java's rule that a method call on an object invokes the implementation belonging to the object's *actual runtime type*, not the type of the reference or the class where the call textually appears. - A method is *overridable* if it is instance-level and not `private`, `static`, or `final` — those three can't be overridden, so calls to them are resolved without dynamic dispatch to a subclass. ## The two facts that collide - (1) Construction runs ***parent-first***: a superclass constructor body runs before the subclass's field initializers and constructor body. - (2) Dynamic dispatch is ***already active*** during construction — `this` already has its final runtime type. So if a superclass constructor calls an overridable method, and a subclass overrides it, the *subclass* version runs — but at that moment the subclass hasn't initialized its own fields. They still hold Java's default values: `0` for numeric primitives, `false` for `boolean`, `null` for references. ## Concrete failure Suppose `Base`'s constructor calls `render()`, and `Derived` overrides `render()` to use a field `Derived.color` that `Derived`'s constructor sets to `"red"`. When you do `new Derived()`: 1. Base's constructor runs first 2. it calls `render()` 3. dynamic dispatch picks `Derived.render()` 4. but `color` is still `null` because Derived's constructor body hasn't run yet 5. NullPointerException (or it silently uses the wrong value). Even more subtly, if Derived sets the field both via initializer (`color = "red"`) and the override reads it during super-construction, the override sees `null`; *then* the initializer runs and sets `"red"`, so the field ends up correct but the override already misbehaved with the wrong value. ## Why Java doesn't 'fix' it Java deliberately keeps dynamic dispatch uniform; it does not switch to the static-type method just because you're inside a constructor (some other languages, like C++/C#, resolve to the constructor's own class type instead — a different trade-off). So in Java **the burden is on the programmer**. ## Safe alternatives - (a) Make the called method `private`, `static`, or `final` so no subclass can override it — the call is then guaranteed to run the intended code. - (b) Don't do real work in the constructor; expose an `init()`/`start()` method the caller invokes after construction, or use a **static factory method** that constructs then initializes. - (c) Make the class `final` if it's not meant to be subclassed, eliminating overrides entirely. - (d) For required setup that depends on subclass state, pass that state up through `super(...)` arguments so the parent has it during construction. ## Effective Java guidance This is Item "Design and document for inheritance or else prohibit it": **constructors must not invoke overridable methods, directly or indirectly.** The same hazard applies to `clone()` and `readObject()` (deserialization), which also behave like constructors in this respect.

  • Does making the field final prevent this bug?
    No. A final field can be assigned during construction; during the superclass constructor it still holds its default value because the subclass hasn't assigned it yet. Finality controls reassignment, not initialization timing.
  • Which methods are safe to call from a constructor?
    Private, static, and final methods — none can be overridden by a subclass, so the call always runs the intended implementation rather than a subclass override against a half-built object.
  • Do clone() and deserialization have the same problem?
    Yes. Both create objects without running normal constructors but still invoke overridable methods on a not-yet-fully-initialized instance, so calling overridable methods from them is equally unsafe.

saying these in an interview costs you the question

  • Claiming the superclass version runs (it doesn't — the override does, via dynamic dispatch)
  • Saying 'just initialize the field before super()' — you can't run subclass field init before super()
  • Thinking final on the field fixes it (the field is still unset during super-construction)
  • Believing this only affects fields set in the constructor body — field *initializers* are also still pending

context