Course contents

A guide to constructors in Java

Default, parameterised, overloaded, copy and private constructors, this(...) and super(...) chaining, the exact initialisation order with static and instance blocks, what javac puts in <init>, exceptions in constructors and leaking this.

FreeMust know for interviewsLesson15 min readCoreReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: building floors from the foundation up
  2. Constructors under the hood
  3. Initialisation order, exactly
  4. Real world: half-built objects in production
  5. Advanced: overridable calls, exceptions and the rules list
  6. How to say it in the interview

🏗️ What is a constructor?

A constructor is the block of code that runs exactly once, right after new allocates an object, to put it into a valid starting state. It has the same name as the class and no return type. For example, new Order(1001, "asha") runs Order(long id, String customer), which makes sure no Order ever exists without an id and a customer.

Constructors look like the easiest topic in the language, and that's why interviewers love them. The basic questions ("what's a default constructor?") take ten seconds. Then come the ones that need a mental model: what does this code print, across a parent and child with static blocks and instance blocks? Why does a subclass field read null inside the parent's constructor? What happens to the object if the constructor throws?

What you’ll learn

  • Know when the compiler adds a default constructor, and why adding one parameterised constructor removes it
  • Chain constructors with this(...) and super(...), and explain why each must be the first statement (until Java 25)
  • Predict the full initialisation order across a hierarchy: static blocks, field initialisers, instance blocks and constructor bodies
  • Read the <init> bytecode and explain where javac copies field initialisers and instance blocks
  • Use copy constructors and private constructors deliberately: defensive copies, utility classes, factories and singletons
  • Avoid the production traps: overridable calls from constructors, **leaking this**, and half-built objects after an exception

For this lesson, we'll focus on every kind of constructor (default, no-arg, parameterised, overloaded, copy, private), chaining with this(...) and super(...), the exact initialisation order and the <init> bytecode behind it, and the production traps: overridable calls, leaking this and exceptions.

Below the line (out of scope)

The 30-second answer

A constructor initialises a newly allocated object. If you write none, the compiler adds a no-arg default constructor; write any constructor and that default disappears. Constructors can be overloaded and chained: this(...) calls another constructor in the same class, super(...) calls the parent's, and one of them is always the first thing that happens, implicitly super() if you write neither. The order is: static initialisers once per class, parent first; then per object, the parent constructor completes, then the child's field initialisers and instance blocks in source order, then the rest of the child's constructor body. In bytecode a constructor is a method called <init>, invoked with invokespecial. Constructors aren't inherited and can't be static, final or abstract. And never call overridable methods or publish this from a constructor, because the object isn't finished yet.

Interview radar

Asked very often: When does Java give you a default constructor, what's the difference between this(...) and super(...), and can a constructor be private? Senior rounds probe: Predict the output of static blocks, instance blocks and constructors across a hierarchy, and explain why a subclass field is null when the parent constructor calls an overridden method. Production angle: A listener registered inside a constructor sees a half-built object on another thread, or an entity class breaks JPA because someone added a constructor and the no-arg one vanished.

01Picture it first: building floors from the foundation up

Think of constructing a building. You can't fit out the third floor before the foundation is poured and the first two floors stand. Java objects are built the same way: Object is the foundation, each parent class is a floor, and your class is the top floor. Calls go down first, bodies run on the way back up.

The diagram below follows new PriorityOrder("asha"), a subclass of the Order class we'll write in a moment, through its this(...) and super(...) hops down to Object, then shows the constructor bodies completing in reverse.

Fig. 1

Calls travel down from PriorityOrder(String) through this to PriorityOrder(String, int), through super to Order(long, String), through this to Order(long, String, List) and through an implicit super to Object; then bodies run back up in numbered order, Object first and the original constructor last

Calls travel down from PriorityOrder(String) through this to PriorityOrder(String, int), through super to Order(long, String), through this to Order(long, String, List) and through an implicit super to Object; then bodies run back up in numbered order, Object first and the original constructor last

Notice two things. Every constructor begins by handing control to another constructor, so the first code that actually finishes is Object(). And the constructor you called is the last one to complete.

02Constructors under the hood

Default and no-arg constructors

If a class declares no constructor at all, javac writes one for you: no parameters, the same access as the class, and a body that only calls super(). That's the default constructor. A no-arg constructor you write yourself is not "default" in the JLS sense, even though people use the words interchangeably.

Try it yourself first

Before reading on, find the two compile errors.

class Customer {
    private final String email;
    Customer(String email) { this.email = email; }
}

class PremiumCustomer extends Customer {
    PremiumCustomer() { }            // implicit super() here
}

Customer c = new Customer();         // ?

Both lines fail with constructor Customer in class Customer cannot be applied to given types; required: String. Declaring Customer(String) removed the free no-arg constructor, so new Customer() has nothing to call. And PremiumCustomer() gets an implicit super() as its first statement, which needs that same missing constructor. The fix is to call super(email) explicitly or add a no-arg constructor to Customer.

Overloading and chaining with this(...)

Overloaded constructors are normal overloads: same name, different parameter lists. The habit worth building is to funnel every overload into one canonical constructor with this(...), so validation lives in one place.

public class Order {
    private final long id;
    private final String customer;
    private final List<String> items;
    private int quantity = 1;                         // field initialiser

    public Order(long id, String customer, List<String> items) {   // canonical
        if (id <= 0) throw new IllegalArgumentException("id must be positive");
        this.id = id;
        this.customer = Objects.requireNonNull(customer);
        this.items = new ArrayList<>(items);          // defensive copy
    }

    public Order(long id, String customer) {
        this(id, customer, List.of());                // delegate, don't duplicate
    }

    public Order(Order other) {                       // copy constructor
        this(other.id, other.customer, other.items);
    }
}

The rules, all enforced by the compiler in Java 21:

  • this(...) or super(...) must be the first statement, and you can't have both. If you write neither, super() is inserted.
  • A cycle (Order() calls this(1), which calls this()) is rejected: recursive constructor invocation.
  • Arguments to this(...) and super(...) can't refer to instance fields or instance methods, because the object isn't initialised yet. Static methods are allowed, which is a useful trick we'll use below.

Java 25 relaxed the first rule: with flexible constructor bodies (JEP 513, previewed from Java 22), you can run statements such as argument validation before super(...), as long as they don't read this.

Copy constructors

The Order(Order other) constructor above is a copy constructor: it builds a new object from an existing one. Because it goes through the canonical constructor, items gets its own ArrayList, so changing one order's items never leaks into the copy. If you assigned this.items = other.items instead, both orders would share one list: a shallow copy. Effective Java (Item 13) prefers copy constructors and copy factories over clone(), because they don't need Cloneable, work with final fields and don't throw checked exceptions. The full story is in Deep copy vs shallow copy in Java.

Private constructors

A private constructor means "only code inside this class (and its nestmates) can call new". It's how you build:

  • Utility classes that should never be instantiated, like java.util.Collections.
  • Static factories such as Order.of(...) or List.of(...), which can cache, return subtypes and have descriptive names (Effective Java Item 1).
  • Singletons, with the instance held in a static field.

Enum constructors are implicitly private, which is why you can never new an enum.

03Initialisation order, exactly

This is the output question you'll see in almost every Java screen.

Try it yourself first

Before reading on, write down what new PaymentService() prints the first time, and then the second time.

class BaseService {
    static { System.out.println("1. BaseService static block"); }
    { System.out.println("3. BaseService instance block"); }
    BaseService() { System.out.println("4. BaseService constructor"); }
}

class PaymentService extends BaseService {
    static { System.out.println("2. PaymentService static block"); }
    private final String region = log("5. PaymentService field initialiser");
    { System.out.println("6. PaymentService instance block"); }
    PaymentService() { System.out.println("7. PaymentService constructor"); }

    static String log(String msg) { System.out.println(msg); return msg; }
}

new PaymentService();   // prints: lines 1 to 7, in order
new PaymentService();   // prints: lines 3 to 7 only

I numbered the lines in the order they print, which gives the answer away on purpose: the numbers are the rule. The timeline below lays out the phases.

Fig. 2

A timeline with a once-per-class phase where BaseService static then PaymentService static blocks run, followed by a per-object phase where the BaseService instance block and constructor run, then the PaymentService field initialiser, instance block and constructor body; a second new skips the static phase

A timeline with a once-per-class phase where BaseService static then PaymentService static blocks run, followed by a per-object phase where the BaseService instance block and constructor run, then the PaymentService field initialiser, instance block and constructor body; a second new skips the static phase

Notice:

  • Static blocks run once per class, parent before child, during class initialisation. The second new skips them.
  • Field initialisers and instance blocks run in source order, as one sequence. Swap the region line and the instance block, and lines 5 and 6 swap.
  • They run after the parent constructor finishes and before your constructor's body. That's the whole reason a parent can't see a child's initialised fields.

What javac puts in <init>

There's no separate "instance initialiser phase" in the JVM. javac copies every field initialiser and instance block, in source order, into each constructor right after its super(...) call. Constructors that start with this(...) don't get a copy, otherwise the code would run twice. Here's javap -c -p Order for the class above, trimmed:

Order(long, String, List);                       // starts with super(): gets the copy
   0: aload_0
   1: invokespecial #1   // Method java/lang/Object."<init>":()V
   4: aload_0
   5: iconst_1
   6: putfield      #7   // Field quantity:I     <- "quantity = 1" lands here
   9: aload_0 ...        //                         then the body: id, customer, items

Order(long, String);                             // starts with this(...): no copy
   0: aload_0 ...
   6: invokespecial #36  // Method "<init>":(JLjava/lang/String;Ljava/util/List;)V

The diagram makes the copying visible.

Fig. 3

On the left, Java source for Order with a field initialiser, an instance block and two constructors; on the right, two generated init methods: the canonical one contains the super call, then the copied initialiser and block, then its body; the delegating one contains only the this call

On the left, Java source for Order with a field initialiser, an instance block and two constructors; on the right, two generated init methods: the canonical one contains the super call, then the copied initialiser and block, then its body; the delegating one contains only the this call

Two more bytecode facts that come up. new and <init> are separate instructions, and the verifier tracks the object as uninitialised between them: you can't call methods on it or store it in a field until invokespecial <init> returns. And a constructor's return type in the descriptor is always V (void), which is why "constructors return the object" is wrong; new produced the reference before the constructor ran.

04Real world: half-built objects in production

In the real world

A team writes a PriceFeedClient whose constructor does bus.register(this) and then sets this.symbols = Set.copyOf(symbols). Under load, the bus thread delivers a price tick between those two lines, and onTick throws NullPointerException on symbols. Rarely, it even sees a stale value after the constructor finishes. Leaking this before construction completes breaks two guarantees at once: the object's invariants, and the Java Memory Model's promise (JLS 17.5) that other threads see final fields correctly only if this didn't escape the constructor. The fix: keep the constructor pure, then register in a static factory once the object is complete.

public final class PriceFeedClient {
    private final Set<String> symbols;

    private PriceFeedClient(Set<String> symbols) {
        this.symbols = Set.copyOf(symbols);           // only state, no side effects
    }

    public static PriceFeedClient start(EventBus bus, Set<String> symbols) {
        PriceFeedClient client = new PriceFeedClient(symbols);
        bus.register(client);                         // published after construction
        return client;
    }
}

The same reasoning covers starting a thread in a constructor, adding this to a static registry, or handing it to an executor. Construction should set fields; side effects come after.

Another real one: JPA, Jackson and many serialisers need a no-arg constructor (JPA requires it to be public or protected). Add a convenience constructor to an entity, the compiler-provided default disappears, and the app fails at startup or on first load with "No default constructor for entity". Declare the no-arg one explicitly, protected if you don't want callers to use it.

05Advanced: overridable calls, exceptions and the rules list

Calling an overridable method from a constructor

Try it yourself first

Before reading on, predict what this prints.

abstract class Notification {
    Notification() {
        System.out.println("Sending: " + render());   // overridable call
    }
    abstract String render();
}

class SmsNotification extends Notification {
    private final String phone;
    SmsNotification(String phone) { this.phone = phone; }
    @Override String render() { return "SMS to " + phone; }
}

new SmsNotification("+91-98xxxxxx01");   // prints: Sending: SMS to null

Even a final field reads null. Virtual dispatch works from the very start, so render() runs the child's override, but the child's constructor body hasn't run yet: the field still holds the default that new wrote. The diagram shows the moment.

Fig. 4

Two stacked frames: the Notification constructor is running and calls render, which dispatches to SmsNotification.render; at that moment the SmsNotification part of the object still has phone equal to null because its constructor body has not started; the output is Sending: SMS to null

Two stacked frames: the Notification constructor is running and calls render, which dispatches to SmsNotification.render; at that moment the SmsNotification part of the object still has phone equal to null because its constructor body has not started; the output is Sending: SMS to null
Where candidates slip

Many candidates say "the parent's constructor calls the parent's version of the method, because the child doesn't exist yet". It's the opposite: the object is a SmsNotification from the moment new runs, so the override is called. What doesn't exist yet is the child's state. That's why Effective Java Item 19 says constructors must not invoke overridable methods, directly or indirectly. Make such methods private, final or static, or do the work after construction.

Exceptions in constructors

If a constructor throws, new never hands the reference to the caller, so normally the half-built object is unreachable and gets collected. Two exceptions to "normally": if the constructor already leaked this (see above), the broken object lives on. And in a non-final class, an attacker's subclass could override finalize() to capture this after the parent's validation threw: the finalizer attack. Finalization is deprecated for removal since Java 18 (JEP 421), but the defensive patterns are still worth knowing: make the class final, or validate before Object's constructor runs, because an object only becomes finalizable after Object() completes.

public class Account {
    private final long balancePaise;

    public Account(long balancePaise) {
        this(validate(balancePaise), null);          // static check runs before super()
    }

    private Account(long balancePaise, Void unused) {
        this.balancePaise = balancePaise;
    }

    private static long validate(long b) {
        if (b < 0) throw new IllegalArgumentException("negative balance");
        return b;
    }
}

A constructor may declare checked exceptions, and a subclass constructor must then declare them too, because it can't wrap super(...) in a try.

Constructor rules, side by side

Constructor Method
Name same as the class anything
Return type none (V in bytecode) required, even void
Inherited ❌ ✅ (non-private)
Allowed modifiers access modifiers only static, final, abstract, synchronized, ...
Invoked by new, this(...), super(...) via invokespecial a call, via invokevirtual, invokestatic, ...
Bytecode name <init> its own name

One trick question hides in that table: public void Order() {} compiles, but it's a method named Order, not a constructor, so new Order() still uses the default constructor and never runs it.

Performance angle

The constructor itself is cheap: allocation in a TLAB is a pointer bump of a few nanoseconds, and the JIT inlines small constructor chains completely, so three this(...) hops cost nothing at steady state. What hurts is work hidden in constructors: a DNS lookup, a file read or a 50 ms connection in a constructor runs on every new, can't be retried cleanly and makes the class untestable. Keep constructors to assignments and validation; push I/O into factories or lifecycle methods.

06How to say it in the interview

When I ask about constructors, I'm listening for four things: the default constructor rule, the chaining rule (this or super first, super() implied), the exact initialisation order with a reason behind it (field initialisers live inside <init> after super), and awareness of the traps: overridable calls and leaking this. Candidates who mention the null from an overridden method, or the final-field visibility guarantee, stand out.

In the interview

"A constructor initialises an object after new has allocated it with default values. If I declare no constructor, javac adds a no-arg one that calls super(); declaring any constructor removes it, which is a common JPA and Jackson gotcha. Each constructor starts with this(...) or super(...), implicitly super(), so construction runs down to Object and the bodies complete on the way back up. Static blocks run once per class, parent first; per object, the parent constructor finishes, then the child's field initialisers and instance blocks in source order, because javac copies them into every right after the super call, and then the child's body. Constructors aren't inherited and can be private for factories, singletons and utility classes. The two traps I avoid are calling overridable methods from a constructor, where the subclass's fields are still null, and letting this escape before construction finishes, which breaks invariants and the final-field guarantee."

On the whiteboard

no ctor written  -> default: no-arg, class's access, calls super()
any ctor written -> default gone (JPA/Jackson need explicit no-arg)
first stmt: this(...) | super(...) | implicit super()   (Java 25: JEP 513 relaxes)
order: static parent -> static child            (once, <clinit>)
       parent ctor done -> child fields + {} in source order -> child body
javac: field inits + {} copied into each <init> that calls super, not this(...)
bytecode: new -> dup -> invokespecial <init>  (returns V)
copy ctor: new Order(other), defensive-copy mutable fields
private ctor: utility, static factory, singleton; enum ctors implicitly private
traps: overridable call in ctor -> child fields null
       leaking this -> half-built object, breaks final-field guarantee (JLS 17.5)
       throw in ctor -> no reference returned; finalizer attack -> final class
not inherited; no static/final/abstract; void Order() is a method
Practise this topicClasses & objects: interview questions & scenarios
Further reading (8)