Constructors in Java
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 intent —
Card.of(rank, suit)vsnew 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 Type | Use Case | Limitation |
|---|---|---|
| Default (no-arg) | Simple classes, frameworks requiring empty constructor | Cannot enforce required fields |
| Parameterized | Mandatory fields must be set | Can get unwieldy with many parameters |
| Private | Singleton, factory pattern — control instantiation | Cannot subclass |
| Copy constructor | Create new instance from existing | Shallow copy unless explicitly deep |
| Chained (this()) | Reduce code duplication between constructors | Must 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()orsuper()is first statement (or implicit) - No object escapes
thisbefore fully initialized
Security Notes
- Validate inputs — reject invalid values before assignment
- Defensive copies — copy mutable objects passed as parameters
- Immutable fields — use
finaland assign in constructor only - Don’t return
thisfrom 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
- Forgetting
super()call — when parent has no default constructor, must explicitly call - Violating the “constructor should do minimal work” rule — heavy initialization in constructors hurts performance
- Creating objects in inconsistent state — don’t let object exist before invariants are established
- Too many constructor overloads — use Builder when parameter combinations become confusing
- 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
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."
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."
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."
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."
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."
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."
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."
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; }`"
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."
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."
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."
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."
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."
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."
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."
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."
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."
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()`."
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."
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
- Fields and Instance Variables — understanding state initialization
- Classes and Objects — the new keyword and instantiation
- Inheritance in Java — super() and parent initialization
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.
Arithmetic Operators in Java
Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.
Array Basics in Java
Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.