Definition
Chaining rules:
- this() delegates within the class. Each convenience constructor adds default values and delegates to a more complete constructor.
- super() terminates the chain. At the end of the this() chain, one constructor calls super() (explicitly or implicitly).
- No circular chains. If A() calls this(1) and A(int) calls this(), the compiler rejects it.
- DRY principle. One primary constructor contains all the logic. Others delegate to it.
Opening a bank account comes in three flavors: basic (name only), standard (name + deposit), premium (name + deposit + overdraft). The premium form fills in all fields. The standard form fills in name and deposit, then passes the form to the premium desk with a default overdraft of zero. The basic form fills in name, then passes to standard with a default deposit of zero. Each delegates to the next, and the premium desk does the actual account creation.
Understanding the Concepts
How does constructor chaining work?
public class Account {
private final String accountId;
private final String holderName;
private BigDecimal balance;
private BigDecimal overdraftLimit;
// Primary constructor: does ALL the work
public Account(String accountId, String holderName, BigDecimal balance, BigDecimal overdraft) {
this.accountId = accountId;
this.holderName = holderName;
this.balance = balance;
this.overdraftLimit = overdraft;
}
// Convenience: no overdraft
public Account(String accountId, String holderName, BigDecimal balance) {
this(accountId, holderName, balance, BigDecimal.ZERO); // chain to primary
}
// Convenience: no balance, no overdraft
public Account(String accountId, String holderName) {
this(accountId, holderName, BigDecimal.ZERO); // chain to 3-arg, which chains to primary
}
}
Account a = new Account("SAV2001", "Rajesh"); // balance=0, overdraft=0
Account b = new Account("SAV2002", "Priya", new BigDecimal("500000")); // overdraft=0
Account c = new Account("SAV2003", "Pankaj", new BigDecimal("1000000"), new BigDecimal("50000"));
The chain: 2-arg → 3-arg → 4-arg (primary). Only the primary constructor assigns fields.
What does the chain look like with inheritance?
public class Account {
protected final String accountId;
public Account(String accountId) {
this.accountId = accountId;
}
}
public class SavingsAccount extends Account {
private double rate;
// Chain within SavingsAccount, then to Account
public SavingsAccount(String accountId) {
this(accountId, 4.0); // this() → other SavingsAccount constructor
}
public SavingsAccount(String accountId, double rate) {
super(accountId); // super() → Account constructor
this.rate = rate;
}
}
Chain: SavingsAccount(String) → this() → SavingsAccount(String, double) → super() → Account(String).
When should you use the Builder pattern instead?
When a class has many optional parameters, constructors become unwieldy. Use Builder:
public class Account {
private final String accountId;
private final String holderName;
private final BigDecimal balance;
private final BigDecimal overdraft;
private final boolean international;
private Account(Builder b) {
this.accountId = b.accountId;
this.holderName = b.holderName;
this.balance = b.balance;
this.overdraft = b.overdraft;
this.international = b.international;
}
public static class Builder {
private final String accountId; // required
private final String holderName; // required
private BigDecimal balance = BigDecimal.ZERO;
private BigDecimal overdraft = BigDecimal.ZERO;
private boolean international = false;
public Builder(String accountId, String holderName) {
this.accountId = accountId;
this.holderName = holderName;
}
public Builder balance(BigDecimal b) { this.balance = b; return this; }
public Builder overdraft(BigDecimal o) { this.overdraft = o; return this; }
public Builder international(boolean i) { this.international = i; return this; }
public Account build() { return new Account(this); }
}
}
// Usage: clear, readable, no telescoping
Account a = new Account.Builder("SAV2001", "Rajesh")
.balance(new BigDecimal("500000"))
.international(true)
.build();
What are common mistakes?
- Circular chaining.
A() { this(1); }andA(int) { this(); }creates infinite recursion. Compiler catches it. - Duplicating logic. Each convenience constructor should only add defaults and delegate, not repeat field assignments.
- Too many constructors. More than 3-4 constructors is a sign you need Builder.
What should a senior engineer know?
- Records have a canonical constructor.
record Account(String id, BigDecimal balance) {}auto-generates the all-args constructor. Add a compact constructor for validation:public Account { Objects.requireNonNull(id); }. - Lombok @Builder generates the Builder pattern automatically.
- Factory methods (
Account.createSavings(...)) are an alternative to multiple constructors and allow returning different subtypes.
Visual Overview
Constructor chaining:
Account(id, name) → this(id, name, ZERO) → this(id, name, ZERO, ZERO)
│
▼
Primary constructor
assigns all fields
With inheritance:
SavingsAccount(id) → this(id, 4.0) → super(id) → Account(id)
Rules:
this() chains within the class
super() chains to the parent
Chain must end at a constructor calling super()
No circular chains (compiler rejects)
Key Takeaway
Constructor chaining means one constructor calls another using this() to reuse initialization logic. The pattern is telescoping: each convenience constructor adds default values and delegates to a more complete constructor. Only the primary constructor does the actual field assignments.
The chain must eventually reach a constructor that calls super() (explicitly or via the compiler's implicit insertion). Circular chains are rejected by the compiler.
For classes with many optional parameters, the Builder pattern is cleaner than telescoping constructors. And records auto-generate the primary constructor, reducing boilerplate.
One sentence to remember: chain convenience constructors to one primary constructor via this(), and the chain must end at a super() call to the parent.
---