Course contents

The super keyword in Java

The three jobs of super: chaining to a parent constructor, calling the parent's method and reading a hidden field, plus initialisation order, invokespecial, Interface.super for clashing defaults and where super is forbidden.

FreeOften askedLesson15 min readCoreReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: building floors from the foundation up
  2. How super works under the hood
  3. Real world: the exception that lost its root cause
  4. Advanced: construction order traps and the bytecode details
  5. How to say it in the interview

🪜 What is the super keyword?

super lets a subclass reach the parent's version of something it has inherited. It has three jobs: super(...) calls a parent constructor, super.fee() calls the parent's implementation of a method you've overridden, and super.channel reads a parent field your class has hidden. For example, a CardPayment can compute its fee as super.fee() + 150, reusing the base fee and adding a card surcharge.

super looks like a small keyword, but interviewers use it to test whether you understand object construction and method dispatch at the same time. Why does class Savings extends Account {} sometimes fail to compile with an error about a constructor you never wrote? Is super.fee() virtual? Can you write super.super.fee()? What's the order of field initializers and constructor bodies across a hierarchy? Each of these has a precise answer, and the precise answer comes from the bytecode.

What you’ll learn

  • How super(...) chains constructors and the exact initialisation order across a hierarchy
  • Why the implicit super() causes a compile error when the parent has no no-arg constructor
  • Why super.method() compiles to invokespecial and is not virtually dispatched, while this stays the subclass
  • How Interface.super.method() resolves clashing default methods
  • Where super is not allowed (static context, super.super) and what Java 25's flexible constructor bodies change

For this lesson, we'll focus on super(...) and constructor chaining, the implicit super() the compiler inserts, super.method() and super.field, the invokespecial instruction behind them, Interface.super.method() for clashing defaults, and where super isn't allowed.

Below the line (out of scope)

The 30-second answer

super(...) calls a parent constructor and must be the first statement of a constructor in Java 21. If you don't write one, the compiler inserts super(), which fails to compile when the parent has no accessible no-arg constructor. super.method() calls the parent's implementation directly: it compiles to invokespecial, so it is not virtually dispatched, though this inside that parent method is still the subclass object. super.field reads a hidden parent field. Interface.super.method() picks one interface's default when two clash. super refers to the current instance, so it can't be used in a static context, and there is no super.super.

Interview radar

Asked very often: Expect questions on super(...) being inserted by the compiler, constructor order, and whether super.method() is virtual. Senior rounds probe: I probe invokespecial, overridable calls from constructors, Interface.super.method() and why there is no super.super. Production angle: This appears in broken exception chaining, framework lifecycle overrides that must call super, and constructor code that reads subclass state too early.

01Picture it first: building floors from the foundation up

Think of constructing a three-storey building. The top-floor contractor can't start until the floor below exists, and that floor can't start until the foundation is poured. So the request goes up the chain ("before I build, the floor below must be built"), and the actual work happens down from the foundation. new CardPayment() works the same way: the CardPayment constructor immediately calls Payment's, which immediately calls Object's, and initialisation then runs from Object down to CardPayment.

The figure shows that round trip for one new CardPayment(). Note that there is still only one object; each constructor initialises its own class's slice of it.

Fig. 1

Constructor chaining for new CardPayment(): calls climb from CardPayment to Payment to Object, then field initializers and constructor bodies run downward from Payment to CardPayment

Constructor chaining for new CardPayment(): calls climb from CardPayment to Payment to Object, then field initializers and constructor bodies run downward from Payment to CardPayment

02How super works under the hood

super(...) and the order of initialisation

Try it yourself first

Before reading on, write down the four lines this prints, in order. Both classes have a field initializer and a constructor body.

class Payment {
    private final long createdAt = log("Payment field initializer");

    Payment() { log("Payment constructor body"); }

    static long log(String step) { System.out.println(step); return System.nanoTime(); }
}

class CardPayment extends Payment {
    private long surcharge = log("CardPayment field initializer");

    CardPayment() { log("CardPayment constructor body"); }
}

new CardPayment();
// prints:
// Payment field initializer
// Payment constructor body
// CardPayment field initializer
// CardPayment constructor body

The CardPayment field initializer runs after the whole Payment constructor, not before it. javap -c CardPayment shows why: the compiler has no separate "field initialisation" step. It copies each field initializer into every constructor that calls super(...), right after that call:

CardPayment();
   0: aload_0
   1: invokespecial #1   // Method Payment."<init>":()V     ← super() first
   4: aload_0
   5: ldc           #7   // String CardPayment field initializer
   7: invokestatic  #9   // Method log:(Ljava/lang/String;)J
  10: putfield      #15  // Field surcharge:J                ← then field initializers
  13: ldc           #19  // String CardPayment constructor body
  15: invokestatic  #9   // Method log:(Ljava/lang/String;)J ← then the body
  18: pop2
  19: return

Notice three things:

  • A constructor is a method named <init> in bytecode, and super() is an invokespecial of the parent's <init> on the same this (aload_0). No second object is created.
  • Field initializers and instance initializer blocks are inlined, in source order, between the super(...) call and the body. Constructors that start with this(...) skip them, because the constructor they delegate to already runs them.
  • The JVM verifier enforces the rule too: until the parent's <init> returns, this is "uninitialized". The only thing allowed is storing into the class's own fields, which javac uses for one hidden purpose: an inner class's reference to its enclosing instance.

The implicit super() and the compile error it causes

If a constructor doesn't start with this(...) or super(...), the compiler inserts super();. If a class declares no constructor at all, the compiler generates a default one whose body is super();. Both are fine until the parent has no no-arg constructor:

class Account {
    private final String id;
    Account(String id) { this.id = id; }   // the only constructor
}

class SavingsAccount extends Account {
    SavingsAccount() { }   // compiler inserts super(); here
}
// error: constructor Account in class Account cannot be applied to given types;
//   required: String
//   found:    no arguments

class SavingsAccount extends Account {} with no constructor at all gives the same error, which confuses people because the line it points at has no constructor in it. The fix is to call the right parent constructor explicitly, SavingsAccount(String id) { super(id); }, or to add a no-arg constructor to Account if a default really makes sense (it usually doesn't for an ID).

The rules for the call itself, in Java 21:

  • super(...) or this(...) must be the first statement, and you can have only one of them.
  • Arguments can't refer to this, instance fields or instance methods ("cannot reference this before supertype constructor has been called"). Static helpers are fine: super(normalise(id)).
  • Java 25 finalised flexible constructor bodies (JEP 513, previewed from Java 22 as JEP 447): you may run statements such as argument validation before super(...), as long as they don't read this. On Java 21 you put that validation in a static helper.

super.method(): the parent's implementation, without dispatch

Try it yourself first

CardPayment overrides both methods and calls super in each. What does new CardPayment().summary() return? Watch which fee() the parent's summary() calls.

class Payment {
    long fee() { return 0; }
    String summary() { return "fee=" + fee(); }
}

class CardPayment extends Payment {
    @Override long fee() { return super.fee() + 150; }
    @Override String summary() { return "[card] " + super.summary(); }
}

System.out.println(new CardPayment().summary());   // prints: [card] fee=150

super.summary() jumps straight into Payment.summary(). But inside it, fee() is an ordinary virtual call on this, and this is still the CardPayment object, so it runs CardPayment.fee(), which in turn calls super.fee() and adds 150. The figure below puts the two kinds of call next to each other.

Fig. 2

Two calls compared: this.fee() compiles to invokevirtual and is looked up in the runtime class's vtable, while super.fee() compiles to invokespecial and is bound to Payment.fee at compile time

Two calls compared: this.fee() compiles to invokevirtual and is looked up in the runtime class's vtable, while super.fee() compiles to invokespecial and is bound to Payment.fee at compile time

That's the key mental model: super.m() is statically bound, but it doesn't change who this is. In bytecode, super.fee() is invokespecial Payment.fee:()J, which the JVM resolves to the implementation in Payment (or the nearest ancestor that has one) without looking at the object's runtime class. A further subclass PremiumCardPayment that overrides fee() doesn't affect what CardPayment's super.fee() calls.

Two consequences come up in interviews. There is no super.super.fee(): you can only reach your direct parent's view, which protects the parent's encapsulation (if Payment wants its grandparent's behaviour bypassed, that's its decision, not yours). And super alone isn't a value: you can't return super; or pass super as an argument. super means "this object, but look up members starting from my parent".

super.field: reaching a hidden field

If a subclass declares a field with the same name as a parent field, it hides the parent's field, and super.channel is how you reach the parent's copy. Since super.channel is resolved by the compiler, the bytecode is a plain getfield Payment.channel. It works, but I treat it as a code smell: fields should be private, and a hidden field is almost always an accident. The inheritance lesson shows why fields are never polymorphic.

Interface.super.method(): choosing between defaults

When a class implements two interfaces whose default methods clash, the compiler forces an override. Inside it, InterfaceName.super.method() calls one specific default:

interface Refundable { default String policy() { return "refund within 7 days"; } }
interface Auditable  { default String policy() { return "keep records 8 years"; } }

class CardPayment implements Refundable, Auditable {
    @Override
    public String policy() {
        return Refundable.super.policy() + "; " + Auditable.super.policy();
    }
}

System.out.println(new CardPayment().policy());   // prints: refund within 7 days; keep records 8 years

This also compiles to invokespecial, against an interface method reference. The named interface must be a direct superinterface of the class (or of the interface, when you write this inside another interface's default method). You can't skip a level and name a grandparent interface, for the same reason there's no super.super.

The diagram below shows the default-method tie break as a dispatch choice the class must make explicitly.

Fig. 3

A class resolving two interface default methods must override the method and can call each direct default with InterfaceName.super.method

A class resolving two interface default methods must override the method and can call each direct default with InterfaceName.super.method

Where super isn't allowed

super always means "the current instance, viewed as its parent", so there must be a current instance:

class CardPayment extends Payment {
    static void reconcile() {
        super.fee();   // error: non-static variable super cannot be referenced from a static context
    }
}

The same applies to static initializer blocks and static nested classes reaching their outer class. Records and enums can't call super(...) explicitly either: a record's canonical constructor implicitly chains to Record's, and an enum constructor implicitly chains to Enum's.

03Real world: the exception that lost its root cause

In the real world

A classic production debugging dead end: a service wraps a failure in a custom PaymentDeclinedException, and the logs show only the wrapper, with no "Caused by:" section. The constructor accepted the cause but never passed it to super, so Throwable never recorded it. When the gateway timed out at 3 a.m., the on-call engineer saw "Payment declined" and nothing about the SocketTimeoutException underneath.

class PaymentDeclinedException extends RuntimeException {
    // ❌ cause is accepted and dropped: stack trace shows no "Caused by:"
    PaymentDeclinedException(String message, Throwable cause) { super(message); }
}

class PaymentDeclinedException2 extends RuntimeException {
    // ✅ Throwable stores the cause and prints it in the stack trace
    PaymentDeclinedException2(String message, Throwable cause) { super(message, cause); }
}

The same pattern shows up wherever a framework expects you to chain to the parent. Android's Activity.onCreate override must call super.onCreate(...), and the framework throws SuperNotCalledException if you forget. A servlet that overrides init(ServletConfig) without calling super.init(config) leaves getServletConfig() returning null. When you override a framework method, check its documentation for "subclasses must call super" before deciding to skip it.

04Advanced: construction order traps and the bytecode details

Calling an overridable method from a constructor. The parent constructor runs before the subclass's field initializers, so an override called from it sees default values:

class Payment {
    Payment() { System.out.println("Created: " + describe()); }
    String describe() { return "payment"; }
}

class CardPayment extends Payment {
    private final String network = "VISA";   // a constant variable: inlined by javac
    private final String last4;
    private int retries = 3;

    CardPayment(String last4) { this.last4 = last4; }

    @Override String describe() { return network + " *" + last4 + " retries=" + retries; }
}

new CardPayment("4242");   // prints: Created: VISA *null retries=0

last4 is null and retries is 0, because neither has been assigned yet. network already looks correct only because a final field initialised with a compile-time constant is a constant variable, and javac inlines "VISA" at every use. Change it to = String.valueOf("VISA") and it prints null too. The rule from Effective Java (Item 19) is simple: constructors must not call methods that subclasses can override. Call private, static or final methods instead.

Where candidates slip

Many candidates say that super(...) "creates the parent object first, then the child object". There's only one object, allocated by the new instruction with room for every field in the hierarchy (all zeroed). super(...) runs the parent's initialisation code on that same object. This matters for the trap above: the subclass's override can run during the parent constructor precisely because the object is already a CardPayment from the moment it's allocated.

invokespecial covers three cases. The JVM uses it for constructors (<init>), super.m() calls and, before Java 11, calls to private methods. Since Java 11 (nestmates, JEP 181), javac emits invokevirtual for private method calls too, but the JVM still binds them directly because a private method can't be overridden. The common thread: invokespecial means "call exactly this implementation, don't search the runtime class".

super in inner classes. An inner class can call its enclosing class's parent method with Outer.super.method(). It's rare in application code, but if you see it in an interview snippet, read it as "the outer instance, viewed as its parent".

Performance angle

super(...) chains and super.m() calls cost effectively nothing after JIT compilation. invokespecial targets a single known method, so it's always inlinable, and Object.<init> is empty. A typical allocation in HotSpot is a thread-local (TLAB) pointer bump of a few nanoseconds, and a three-level constructor chain inlines into that same compiled code. Don't flatten a hierarchy for speed; flatten it if the design is wrong.

this super
Refers to the current object the current object, looked up from the parent class
Constructor call this(...): another constructor of this class super(...): a constructor of the direct parent
Method call this.fee(): invokevirtual, runtime class decides super.fee(): invokespecial, parent's implementation
Field access own field (or inherited one) the parent's field, even if hidden
Usable as a value yes (return this;) no
Static context not allowed not allowed

05How to say it in the interview

When I ask about super, I'm listening for construction order and dispatch. A good answer says that super(...) must come first (and that Java 25 relaxed this), explains the compile error from the implicit super(), and knows that super.m() is a non-virtual call while this inside the parent's method is still the subclass. Bonus points for invokespecial and for spotting the "overridable method in a constructor" trap.

In the interview

"super has three uses. super(...) chains to a parent constructor; in Java 21 it must be the first statement, and if I omit it the compiler inserts super(), which won't compile if the parent has no no-arg constructor. Construction is one object: new allocates and zeroes all fields, then each constructor runs top-down, and javac inlines field initializers right after the super call. super.m() calls the parent's implementation; it compiles to invokespecial, so there's no virtual lookup, but this inside that method is still the subclass, so calls it makes are dispatched normally. super.field reads a hidden field, and Interface.super.m() picks a default when two interfaces clash. There's no super.super, and no super in static code. The trap I watch for is calling an overridable method from a constructor, because the override sees uninitialised fields."

On the whiteboard

super(...)        first statement (Java 21); one of this(...)/super(...)
omitted           → compiler inserts super()  → parent needs no-arg ctor
order             new (alloc + zero) → Object.<init> → P init → C init
                  each <init>: super() → field inits → ctor body
super.m()         invokespecial P.m   (not virtual)
                  but `this` inside P.m is still C → its calls dispatch
super.f           parent's hidden field (getfield P.f)
I.super.m()       pick a default; I must be a direct superinterface
not allowed       super.super, static context, `return super`
trap              overridable call in ctor → sees null / 0
Java 25           JEP 513: statements before super() (no `this`)
Practise this topicInheritance & polymorphism: interview questions & scenarios
Further reading (8)