Constructors in Java

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

published: reading time: 15 min read author: Geek Workbench

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()

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)

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

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()

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

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?

Model Answer: "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?

Model Answer: "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?

Model Answer: "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?

Model Answer: "`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?

Model Answer: "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?

Model Answer: "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()?

Model Answer: "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?

Model Answer: "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?

Model Answer: "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?

Model Answer: "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?

Model Answer: "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?

Model Answer: "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?

Model Answer: "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?

Model Answer: "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?

Model Answer: "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?

Model Answer: "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?

Model Answer: "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?

Model Answer: "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?

Model Answer: "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?

Model Answer: "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