Constructors in Java

Master Java constructors: default, parameterized, overloading, and constructor chaining with this() and super().

published: reading time: 19 min read author: Geek Workbench
Quick Summary

Master Java constructors: default, parameterized, overloading, and constructor chaining with this() and super().

Constructors in Java

Constructors are the gates through which objects enter existence. They initialize the raw memory that new allocates and set up the invariants your class depends on.

Introduction

Constructors are the initialization gate through which every Java object passes at creation time. When new allocates raw memory, the constructor runs to transform that memory into a valid object — setting required fields, validating parameters, establishing invariants, and invoking any parent class initialization via super(). Getting this wrong produces objects that exist in invalid states, causing bugs that are difficult to trace because the corruption happens silently before any code can check the object’s health. A constructor that accepts negative values for a balance field creates an account that looks valid until someone tries to process a transaction.

The mechanics carry specific constraints that catch developers unfamiliar with them. this() and super() — the constructor chaining calls — must be the first statement in any constructor body. If you omit an explicit call and the parent class has a no-arg constructor, Java inserts super() implicitly; but if the parent only has parameterized constructors, your code fails to compile with no diagnostic beyond “constructor call required.” Calling one constructor from another via this(args) enables constructor overloading with delegation rather than duplication, but recursive calls (A calling B calling A) are a compile-time error, not a runtime exception.

The most dangerous constructor anti-pattern is the this-escape: passing this to another object before the constructor finishes. A listener registered in a constructor can see the partially initialized object if that listener fires before the constructor completes. If the listener calls back into the object, it sees fields at their default values, not the values the constructor was in the process of setting. This post covers default and parameterized constructors, constructor chaining via this(), the super() call for parent initialization, copy constructors for cloning, why this() must be first, and the security-conscious pattern of defensive copying for mutable parameters.

When to Use

Use constructors when:

  • Mandatory fields must be set at creation time — use a constructor that requires them
  • Validating object creation — reject invalid states before the object exists
  • Setting up dependencies — inject collaborators that the object needs
  • Providing convenience — multiple constructors for different initialization paths
public class Player {
    private final String username;
    private int health;
    private int score;

    // Primary constructor — requires username
    public Player(String username) {
        this.username = username;
        this.health = 100;
        this.score = 0;
    }

    // Convenience constructor — delegates to primary with defaults
    public Player(String username, int health) {
        this(username);  // Delegation via this()
        this.health = health;
    }
}

When Not to Use

Avoid constructors for:

  • Complex object creation — use the Builder pattern instead
  • Creating objects with many optional parameters — too many constructor overloads
  • Creating immutable objects with many fields — Builder or factory methods
  • When factory methods better express intentCard.of(rank, suit) vs new Card(rank, suit)
// Too many parameters — error-prone for callers
public Config(String host, int port, String user, String pass, boolean ssl, int timeout, boolean retries);

// Better: Builder pattern
public class ConfigBuilder {
    public ConfigBuilder host(String host) { ... return this; }
    public ConfigBuilder port(int port) { ... return this; }
    public Config build() { return new Config(this); }
}

Constructor Overloading — Mermaid Diagram

classDiagram
    class Player {
        +Player(String username)$
        +Player(String username, int health)$
        +Player(String username, int health, int score)$
        +Player(Player other)$
    }
    note for Player "Constructor Chaining\nthis() -> this(username) -> this(username, health)"

Failure Scenarios

1. Forgetting to Call this() or super()

Java adds an implicit super() call for you — but only when the parent class has a no-arg constructor. The moment a parent declares any constructor, Java assumes you meant to take control of initialization and stops generating the default. This means a derived class with no explicit constructor call will fail to compile if its parent only has parameterized constructors. The compiler error points at the call site with no mention of the real cause: the missing super().

The fix is to call super(name) explicitly when the parent requires parameters. If the parent has multiple constructors, pick the one that matches the state your subclass needs to inherit. The example below shows a Base class with a single parameterized constructor and a Derived class that fails to compile without an explicit super(name) call.

public class Base {
    public Base(String name) { }
}

public class Derived extends Base {
    public Derived() {
        // super(); // IMPLICIT — but only if Base has no-arg constructor
        // If Base only has parameterized constructor, this fails
    }
}

// Fix: explicitly call super(name)
public class Derived extends Base {
    public Derived(String name) {
        super(name);  // Explicit call required
    }
}

2. Constructor Calling Constructor (Infinite Loop)

this() enables constructor delegation, but it comes with a hard constraint: you cannot chain recursively. If constructor A calls this(args) which routes to constructor B, and constructor B calls this(args) which routes back to A, the compiler detects the cycle and rejects the code. Unlike infinite loops in regular methods which produce a stack overflow at runtime, constructor cycles are caught at compile time — Java treats them as a structural error rather than a runtime hazard.

The cycle does not have to be direct. A calling B calling C calling A is equally invalid. The compiler performs a static analysis of constructor call graphs and flags any path that loops back to a constructor already on the call stack. The fix is to eliminate the cycle by designating one constructor as the terminal point that does the actual initialization, and having all others delegate to it without calling each other.

public class Broken {
    private int value;

    public Broken(int value) {
        this(value);  // ERROR: recursive constructor invocation
    }

    public Broken() {
        this(42);  // Delegates to the above
    }
}

3. Object Escaping this Before Initialization

The this-escape happens when a constructor hands out a reference to this before the object is fully initialized. The object exists in memory, but its invariants have not been established yet. Any code that receives this during construction can observe and interact with fields at their default values instead of the values the constructor was assigning. If that code calls back into the object, it operates on an inconsistent state.

The most common form is registering a listener or callback in the constructor and passing this as the argument. The listener receives the reference while the constructor is still running. If the listener fires synchronously during registration, it sees a partially constructed object. If it fires asynchronously, the object may be in a fully initialized state by the time it fires — but the window of vulnerability still existed during construction.

The inner class pattern in the example below illustrates the timing issue. The MyListener constructor receives this after name has been assigned, so the outer reference is safe in that specific case. But if name had not yet been assigned when registerListener was called, the listener would observe name as null. The defensive approach is to never pass this to another object until all fields are set and any setup logic has completed.

public class ThisEscape {
    private final String name;

    public ThisEscape(EventSource source) {
        this.name = "initialized";
        // DON'T: pass 'this' to another object before fields are set
        source.registerListener(new MyListener(this));  // Listener might see uninitialized object
    }

    private class MyListener implements Listener {
        private final ThisEscape outer;
        public MyListener(ThisEscape outer) {
            this.outer = outer;  // At this point, name is already set — safe
        }
    }
}

Trade-off Table

Constructor TypeUse CaseLimitation
Default (no-arg)Simple classes, frameworks requiring empty constructorCannot enforce required fields
ParameterizedMandatory fields must be setCan get unwieldy with many parameters
PrivateSingleton, factory pattern — control instantiationCannot subclass
Copy constructorCreate new instance from existingShallow copy unless explicitly deep
Chained (this())Reduce code duplication between constructorsMust be first statement

Code Snippets

Constructor Chaining with this()

Constructor chaining via this() lets you define multiple initialization paths that all ultimately invoke the same base constructor. One constructor does the actual field assignments. The others delegate to it, each filling in defaults for some parameters. This means you write the initialization logic once, and every overload routes through it.

The constraint is that this() must be the first statement in the constructor body. This is not arbitrary syntax — it ensures the object is initialized in a predictable order. When you call this(url, headers, body), control transfers to the matching constructor, which runs its own this() call if it also chains further up, until the base constructor actually assigns the fields. No code in a delegating constructor runs after the this() call returns.

The HttpRequest example below shows a typical chain. The four-argument constructor is the base — it assigns all fields directly. Each shorter constructor chains upward, filling in a default value for the missing parameter. A caller who only has a URL gets a fully initialized object with a 30-second timeout, empty headers, and no body. The chain is never circular because each step moves toward more arguments, not back toward fewer.

public class HttpRequest {
    private final String url;
    private final int timeout;
    private final Map<String, String> headers;
    private final String body;

    // Base constructor — does real work
    public HttpRequest(String url, int timeout, Map<String, String> headers, String body) {
        this.url = url;
        this.timeout = timeout;
        this.headers = headers;
        this.body = body;
    }

    // Chain to base with default timeout
    public HttpRequest(String url, Map<String, String> headers, String body) {
        this(url, 30000, headers, body);
    }

    // Chain with default headers
    public HttpRequest(String url, String body) {
        this(url, 30000, Map.of(), body);
    }

    // Chain with minimum required
    public HttpRequest(String url) {
        this(url, 30000, Map.of(), null);
    }
}

Copy Constructor

A copy constructor accepts an existing instance of the same class and produces a new, independent instance with the same field values. The pattern is new Player(existingPlayer) — explicit about intent, easy to read, and creates a clone without the ambiguity of clone() or serialization-based approaches. Copy constructors are useful when you need to pass a modified copy of an object into a method without letting the caller hold a reference that can mutate your internal state.

The key design decision is shallow versus deep copy. For immutable fields like String and primitives, the value is copied directly — both instances hold independent references to the same immutable value, which is fine. For mutable reference types like List<String>, you need to decide whether to copy the reference or copy the object itself. Storing the direct reference (this.inventory = inventory) means external code holding a reference to the original list can modify your internal state. The defensive copy approach (new ArrayList<>(inventory)) creates a new list with the same elements, so external changes cannot reach your internal state.

The Player example demonstrates this. The parameterized constructor takes ownership of a defensive copy of the inventory list. The copy constructor then delegates to the parameterized constructor, passing the fields from the source object. The result is a new Player that is functionally identical to the original but completely independent — mutations to the original’s inventory do not affect the copy.

public class Player {
    private final String name;
    private int health;
    private final List<String> inventory;

    public Player(String name, int health, List<String> inventory) {
        this.name = name;
        this.health = health;
        this.inventory = new ArrayList<>(inventory);  // Defensive copy
    }

    // Copy constructor
    public Player(Player other) {
        this(other.name, other.health, other.inventory);
    }
}

Observability Checklist

  • Required fields enforced via constructor parameters
  • All constructor parameters validated before assignment
  • Reference types defensively copied in constructor
  • this() or super() is first statement (or implicit)
  • No object escapes this before fully initialized

Security Notes

  • Validate inputs — reject invalid values before assignment
  • Defensive copies — copy mutable objects passed as parameters
  • Immutable fields — use final and assign in constructor only
  • Don’t return this from constructor — enables partially constructed object access
public class SecureRequest {
    private final List<String> allowedOrigins;

    public SecureRequest(List<String> allowedOrigins) {
        // Defensive copy — external list cannot affect our internal state
        this.allowedOrigins = List.copyOf(allowedOrigins);
    }

    public List<String> getAllowedOrigins() {
        return allowedOrigins;  // Already immutable, safe to return
    }
}

Pitfalls

  1. Forgetting super() call — when parent has no default constructor, must explicitly call
  2. Violating the “constructor should do minimal work” rule — heavy initialization in constructors hurts performance
  3. Creating objects in inconsistent state — don’t let object exist before invariants are established
  4. Too many constructor overloads — use Builder when parameter combinations become confusing
  5. Not defensive copying mutable parameters — storing direct references to mutable objects
// Bad: storing reference to mutable object
public class Cache {
    private List<String> entries;

    public Cache(List<String> entries) {
        this.entries = entries;  // External list can modify our data!
    }
}

// Good: defensive copy
public class Cache {
    private final List<String> entries;

    public Cache(List<String> entries) {
        this.entries = List.copyOf(entries);  // Safe — we own our data
    }
}

Quick Recap

  • Default constructor = provided if no constructors defined, initializes fields to defaults
  • Parameterized constructor = requires specific values at creation
  • Constructor overloading = multiple constructors with different signatures
  • this(args) = chain to another constructor in same class (must be first line)
  • super(args) = chain to parent constructor (must be first line, implicit if omitted)
  • Copy constructor = new Player(existingPlayer) pattern for cloning

Interview Questions

1. What happens if you don't define any constructor in a class?
Java provides a default (no-arg) constructor automatically, which initializes all fields to their default values (0, false, null). This default constructor is only generated if you don't define any constructors yourself. Once you define any constructor, the default is suppressed."

2. What is constructor chaining?
Constructor chaining is when one constructor calls another constructor in the same class using `this(args)`. This allows you to have one constructor that does the real work while others delegate to it, reducing code duplication. The `this()` call must be the first statement in the constructor."

3. Can a constructor call a method?
Yes, but be careful — overridable (non-final, non-static) methods will execute on the partially initialized object. Never call overridable methods from a constructor, as subclasses may see inconsistent state. If you must call a method, make it final or static."

4. What is the difference between `this()` and `super()` in constructors?
`this()` calls another constructor in the same class (constructor overloading delegation). `super()` calls a constructor in the parent class (constructor inheritance). Both must be the first statement in the constructor, and you cannot use both in the same constructor."

5. Why should mutable object parameters be copied in constructors?
If you store a direct reference to a mutable object passed as a parameter, external code can modify that object and affect your internal state. By creating a defensive copy (like `List.copyOf()` or `new ArrayList<>(list)`), you own your own copy and external changes cannot affect you."

6. What happens when a constructor throws an exception during object creation?
The partially constructed object is never returned to the caller. If the object was assigned to a variable before the exception occurred, that variable gets the exception. There is no garbage collection issue because the object was never completed — no leak occurs."

7. Can constructors be called recursively by using this()?
No — `this()` or `super()` must be the first statement, so recursive constructor calls cause a compile error. Java detects constructor chaining cycles at compile time and rejects the code."

8. What is a copy constructor and when would you use one?
A copy constructor takes an instance of the same class and creates a new independent copy. It is useful for cloning objects — especially important for defensive copying of mutable fields. Example: `public Player(Player other) { this.name = other.name; this.health = other.health; }`"

9. Why must this() or super() be the first statement in a constructor?
Java requires parent initialization before subclass initialization to ensure invariants are established first. Making it the first statement prevents partially initialized objects from being used. The compiler inserts implicit `super()` when no explicit call is present."

10. Can you have a constructor that returns a value?
Constructors do not have a return type — not even void. Using `return;` at the end is allowed but does not return anything. The `new` operator returns the newly created object reference, not anything from the constructor itself."

11. What is the difference between default and parameterized constructors?
A default no-arg constructor is provided by Java only if no constructors are defined. Parameterized constructors accept arguments to initialize fields with specific values. Defining any constructor suppresses the default constructor generation."

12. What happens if you call this() and super() in the same constructor?
Compilation error — `this()` and `super()` cannot both appear as the first statement. Both represent constructor chaining but to different targets (same class vs parent class). You must choose one: either chain to another constructor in the same class or to the parent constructor."

13. What is the constructor execution order in a class hierarchy?
Parent constructor executes first, then child fields are initialized, then the child constructor body runs. If the parent has a parameterized constructor, the child must explicitly call `super(...)` as the first line. Static initializers run once at class load time, before any object creation."

14. What is a private constructor and what pattern uses it?
Private constructors prevent external code from directly instantiating the class. The Singleton pattern uses a private constructor with a static `getInstance()` method. The Factory pattern also uses private constructors to control object creation through factory methods rather than direct instantiation."

15. How does the Builder pattern improve constructor usability?
The Builder pattern allows creating objects with many optional parameters without constructor overload explosion. It provides readable method chaining: `new UserBuilder().name(\"X\").email(\"y\").build()`. Each setter returns the builder itself for a fluent API; `build()` validates and creates the object."

16. What happens to instance fields if a constructor fails mid-execution?
The object is never fully constructed and the reference is never returned. Fields initialized before the failure point retain their values; fields initialized after the failure point do not exist. Java ensures no partially constructed object escapes by not returning the reference."

17. Can an abstract class have a constructor and why?
Yes — abstract classes can have constructors for initializing common parent state that all subclasses inherit. Subclasses must call `super()` in their constructor, which invokes the abstract class constructor. Although you cannot instantiate an abstract class directly, subclasses inherit the initialization logic."

18. What is constructor overloading and how does it relate to this() chaining?
Constructor overloading is having multiple constructors with different parameter lists in the same class. The `this(args)` method allows one constructor to call another in the same class, reducing code duplication. The common pattern is one \"master\" constructor that does the real work while others delegate via `this()`."

19. How do you prevent the `this` reference from escaping during construction?
Do not pass `this` to another object (as a listener, callback, or stored reference) before initialization completes. Use a private static inner class or factory method that completes construction before returning the object. Pass `this` only after all fields are set and invariants are established."

20. What is the relationship between field initialization and constructor execution order?
Field declarations execute before the constructor body (in declaration order). Instance initializer blocks run after field initializations but before the constructor body. After the implicit or explicit `super()` call, the constructor body executes. Parent fields are ready before child initialization begins."

Further Reading

Conclusion

Constructors are the initialization gate through which every Java object passes at creation time. They ensure that objects enter the world in a valid state — setting required fields, validating parameters, and establishing invariants before any other code can interact with the object.

The distinction between default and parameterized constructors matters for API design: default constructors suit simple classes where all fields have sensible defaults, while parameterized constructors enforce that critical data is supplied at creation. Constructor overloading provides multiple initialization paths, with this() chaining reducing duplication by directing all paths to a single constructor that does the actual work.

The this() call for constructor chaining and super() call for parent initialization must be the first statement in a constructor. If omitted and the parent has a no-arg constructor, Java inserts an implicit super(); but if the parent only has parameterized constructors, you must call super(args) explicitly or compilation fails.

Security-conscious constructor design validates all inputs before assignment, makes defensive copies of mutable parameters, and uses final fields for anything that should not change after construction. This prevents invalid state from ever existing and blocks external code from corrupting internal state through stored references.

Constructors tie directly into the object lifecycle. After a constructor completes, the object is ready for use. If a constructor throws an exception, the object is never returned to the caller — this prevents partially constructed objects from escaping. This initialization protocol builds on the field initialization concepts covered in Fields and Instance Variables, and constructors themselves are invoked by the new keyword as detailed in Classes and Objects.

Category

Related Posts

Abstract Classes in Java

Learn about partially implemented classes that define contracts for subclasses using abstract methods and concrete implementations.

#java-abstract-classes #java #java-fundamentals

Arithmetic Operators in Java

Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.

#java-arithmetic-operators #java #java-fundamentals

Array Basics in Java

Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.

#java-array-basics #java #java-fundamentals