☕ Java

Java Constructor — Syntax, Types, Examples & Best Practices

Everything you need to know about Java constructors — default, no-arg, parameterized, copy constructors, constructor overloading, this() and super() calls, constructor chaining, private constructor, constructors in inheritance, anti-patterns, and real-world production code examples.

📅

Last Updated

March 2026

⏱️

Read Time

22 min

🎯

Level

Beginner

🏷️

Chapter

14 of 35

What is a Constructor in Java?

A constructor in Java is a special block of code that is automatically invoked when an object is created using the new keyword. Its primary purpose is to initialize the newly created object — assigning values to fields, validating inputs, allocating resources, and ensuring the object starts in a valid, consistent state. Without constructors, every newly created object would have only default field values (0, false, null) and no setup logic.

In real applications, constructors are everywhere: when a User object is created, its name, email, and role must be set; when a BankAccount is opened, the account number and initial balance must be established; when a database connection is initialized, the URL and credentials must be configured. All of this setup happens in constructors — the entry point of every object's life.

📌
Syntax

accessModifier ClassName(parameters) { // initialization code } Example: public Student(String name, int age) { this.name = name; this.age = age; }

📋
Constructor Properties

1. Name MUST match class name exactly (case-sensitive). 2. No return type — not void, not int, nothing. 3. Can have access modifiers: public, protected, private, package-private. 4. Can throw exceptions (checked or unchecked). 5. Can call other constructors via this() or super() as the FIRST statement.

🔁
Types of Constructors

1. Default constructor — auto-generated by compiler when none is defined. 2. No-arg constructor — explicitly written with no parameters. 3. Parameterized constructor — takes one or more parameters. 4. Copy constructor — takes an object of the same class and copies its state. All of these can be combined via constructor overloading.

☕ JavaConstructorBasics.java
public class Student {

    // Fields
    String name;
    int    age;
    String course;

    // ✅ Parameterized constructor
    public Student(String name, int age, String course) {
        this.name   = name;
        this.age    = age;
        this.course = course;
    }

    public void display() {
        System.out.println(name + " | Age: " + age + " | Course: " + course);
    }

    public static void main(String[] args) {

        // Constructor is called automatically when 'new' is used
        Student s1 = new Student("Priya", 20, "B.Tech CSE");
        Student s2 = new Student("Rahul", 22, "MBA");

        s1.display(); // Priya | Age: 20 | Course: B.Tech CSE
        s2.display(); // Rahul | Age: 22 | Course: MBA

        // ❌ No return type — this would be a method, not a constructor:
        // public void Student() { } // This is a method named Student, NOT a constructor
    }
}

Rules of Constructors — What the Compiler Enforces

The Java compiler enforces precise rules for constructors. Violating any of these causes a compile-time error. Understanding them prevents confusion between constructors and methods, and avoids common initialization bugs.

1️⃣
Rule 1 — Name Must Match Class Name

A constructor's name must be EXACTLY the same as the class name, including case. 'public student()' in class 'Student' is NOT a constructor — it is treated as a method with a missing return type and causes a compile error. Case matters: 'Student' ≠ 'student'.

2️⃣
Rule 2 — No Return Type

Constructors have NO return type — not void, not int, not String, nothing. If you add a return type (even void), it becomes a regular method named after the class — NOT a constructor. The compiler will not treat it as a constructor and the class will get an auto-generated default constructor instead.

3️⃣
Rule 3 — Cannot Be static, final, or abstract

Constructors cannot be declared static (constructors belong to object creation, not the class itself), final (constructors are not inherited so final has no meaning), or abstract (abstract implies no body, but constructors must have a body). These modifiers cause compile errors on constructors.

4️⃣
Rule 4 — this() or super() Must Be First

If a constructor calls this() (another constructor in same class) or super() (parent class constructor), that call MUST be the very first statement. You cannot place any other code before it. Also, this() and super() cannot both appear in the same constructor — only one is allowed.

5️⃣
Rule 5 — Constructors Are NOT Inherited

Constructors are NOT inherited by subclasses. A subclass does not get the parent's constructors automatically. However, the parent's constructor IS always called — either explicitly via super(args) or implicitly via a no-arg super() inserted by the compiler. If the parent has no no-arg constructor, the subclass MUST call super(args) explicitly.

6️⃣
Rule 6 — Default Constructor Auto-Generated

If a class defines NO constructors at all, the compiler automatically provides a default no-arg constructor: ClassName() { super(); }. If you define ANY constructor (even just one parameterized constructor), the compiler stops providing the default. You must define the no-arg constructor explicitly if you need it.

Default Constructor — Java's Automatic Gift

The default constructor is a no-arg constructor automatically generated by the Java compiler when a class declares no constructors at all. It has no parameters, an empty body (aside from an implicit super() call to the parent class), and is given public access if the class is public. It exists purely to allow objects to be created without any arguments. The moment you define even one constructor yourself, the compiler stops generating the default constructor.

☕ JavaDefaultConstructor.java
// ✅ Class with NO explicit constructor
// Compiler auto-generates: public Box() { super(); }
public class Box {
    double length;
    double width;
    double height;
    // No constructor defined — compiler provides default
}

// ✅ Class with ONE parameterized constructor
// Compiler does NOT generate a default constructor here
public class Circle {
    double radius;
    public Circle(double radius) {
        this.radius = radius;
    }
}

class DefaultConstructorDemo {
    public static void main(String[] args) {

        // ✅ Box has default constructor — works fine
        Box b = new Box();
        System.out.println(b.length);  // 0.0 (default value for double)
        System.out.println(b.width);   // 0.0
        b.length = 10.0;
        b.width  = 5.0;
        b.height = 3.0;
        System.out.println("Volume: " + (b.length * b.width * b.height)); // 150.0

        // ✅ Circle with parameterized constructor
        Circle c = new Circle(7.0);
        System.out.println("Area: " + Math.PI * c.radius * c.radius); // 153.93...

        // ❌ Circle with no args — COMPILE ERROR
        // Circle c2 = new Circle(); // ERROR: no suitable constructor found
        // Because Circle defines a parameterized constructor,
        // the compiler no longer provides a default no-arg constructor
    }
}

No-Arg Constructor — Explicit Zero-Parameter Constructor

A no-arg constructor is a constructor with no parameters that is explicitly written by the programmer. Unlike the compiler-generated default constructor (which has an empty body), a no-arg constructor can contain initialization logic — setting default values, logging, or validating the environment. Many Java frameworks (Hibernate, Spring, Jackson) require a no-arg constructor to create objects via reflection.

☕ JavaNoArgConstructor.java
public class DatabaseConfig {

    private String host;
    private int    port;
    private String dbName;
    private int    connectionTimeout;

    // ✅ Explicit no-arg constructor — sets sensible defaults
    public DatabaseConfig() {
        this.host              = "localhost";
        this.port              = 5432;
        this.dbName            = "default_db";
        this.connectionTimeout = 30;
        System.out.println("DatabaseConfig created with defaults");
    }

    // ✅ Parameterized constructor — full custom config
    public DatabaseConfig(String host, int port, String dbName, int timeout) {
        this.host              = host;
        this.port              = port;
        this.dbName            = dbName;
        this.connectionTimeout = timeout;
    }

    @Override
    public String toString() {
        return host + ":" + port + "/" + dbName + " (timeout=" + connectionTimeout + "s)";
    }

    public static void main(String[] args) {

        // Uses no-arg constructor — default config
        DatabaseConfig dev = new DatabaseConfig();
        System.out.println(dev); // localhost:5432/default_db (timeout=30s)

        // Uses parameterized constructor — production config
        DatabaseConfig prod = new DatabaseConfig("prod.db.company.com", 5432,
                                                  "prod_db", 60);
        System.out.println(prod); // prod.db.company.com:5432/prod_db (timeout=60s)
    }
}

Parameterized Constructor — Initializing with Custom Values

A parameterized constructor is a constructor that accepts one or more parameters to initialize an object with specific values at creation time. This is the most commonly used constructor type in production Java code. It enforces that an object is always created with meaningful data — preventing partially initialized, invalid objects from ever existing.

☕ JavaParameterizedConstructor.java
public class Employee {

    private final String employeeId;   // final — set once in constructor
    private       String name;
    private       String department;
    private       double salary;

    // ✅ Parameterized constructor with validation
    public Employee(String employeeId, String name, String department, double salary) {
        // Guard clauses — validate before assigning
        if (employeeId == null || employeeId.isBlank())
            throw new IllegalArgumentException("Employee ID cannot be blank");
        if (name == null || name.isBlank())
            throw new IllegalArgumentException("Employee name cannot be blank");
        if (salary < 0)
            throw new IllegalArgumentException("Salary cannot be negative");

        this.employeeId = employeeId;
        this.name       = name;
        this.department = department;
        this.salary     = salary;
    }

    @Override
    public String toString() {
        return "[" + employeeId + "] " + name + " | " + department
               + " | ₹" + String.format("%.2f", salary);
    }

    // Getters
    public String getEmployeeId() { return employeeId; }
    public String getName()        { return name; }
    public double getSalary()      { return salary; }

    public static void main(String[] args) {

        Employee e1 = new Employee("EMP-001", "Ananya Sharma", "Engineering", 85000);
        Employee e2 = new Employee("EMP-002", "Vikram Nair",   "Finance",     72000);

        System.out.println(e1); // [EMP-001] Ananya Sharma | Engineering | ₹85000.00
        System.out.println(e2); // [EMP-002] Vikram Nair   | Finance     | ₹72000.00

        // ❌ Invalid creation — constructor throws exception
        try {
            Employee bad = new Employee("", "Test", "IT", -100);
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
            // Error: Employee ID cannot be blank
        }
    }
}

Copy Constructor — Creating an Object from Another Object

A copy constructor is a constructor that takes an object of the same class as its parameter and creates a new object with the same field values — a copy. Java does not have a built-in copy constructor (unlike C++), but you can write one explicitly. It is essential for creating deep copies of objects containing mutable fields, preventing unintended sharing of state between the original and the copy.

☕ JavaCopyConstructor.java
import java.util.ArrayList;
import java.util.List;

public class ShoppingCart {

    private String   customerId;
    private List<String> items;   // Mutable — needs deep copy
    private double   totalAmount;

    // Regular constructor
    public ShoppingCart(String customerId) {
        this.customerId   = customerId;
        this.items        = new ArrayList<>();
        this.totalAmount  = 0.0;
    }

    // ✅ Copy constructor — creates a DEEP copy
    public ShoppingCart(ShoppingCart other) {
        this.customerId  = other.customerId;             // String — immutable, safe to share
        this.items       = new ArrayList<>(other.items); // ✅ New list — deep copy
        this.totalAmount = other.totalAmount;            // primitive — copied by value
    }

    public void addItem(String item, double price) {
        items.add(item);
        totalAmount += price;
    }

    @Override
    public String toString() {
        return "Cart[" + customerId + "] Items: " + items + " | Total: ₹" + totalAmount;
    }

    public static void main(String[] args) {

        ShoppingCart original = new ShoppingCart("CUST-101");
        original.addItem("Laptop", 75000);
        original.addItem("Mouse",  1500);
        System.out.println("Original: " + original);
        // Original: Cart[CUST-101] Items: [Laptop, Mouse] | Total: ₹76500.0

        // ✅ Copy constructor — creates independent copy
        ShoppingCart copy = new ShoppingCart(original);
        copy.addItem("Keyboard", 3000); // Modifying copy

        System.out.println("Original: " + original);
        // Original: Cart[CUST-101] Items: [Laptop, Mouse] | Total: ₹76500.0 ← UNCHANGED
        System.out.println("Copy:     " + copy);
        // Copy:     Cart[CUST-101] Items: [Laptop, Mouse, Keyboard] | Total: ₹79500.0

        // ❌ SHALLOW COPY PROBLEM — without copy constructor:
        ShoppingCart ref = original; // Same object — NOT a copy
        ref.addItem("Monitor", 20000);
        System.out.println("Original after ref change: " + original);
        // Original: Cart[CUST-101] Items: [Laptop, Mouse, Monitor] — CHANGED!
    }
}

Constructor Overloading — Multiple Ways to Create an Object

Constructor overloading is defining multiple constructors in the same class with different parameter lists. It follows the exact same rules as method overloading — the constructors must differ in the number, type, or order of parameters. This allows objects to be created in multiple ways — with all data, with partial data (using defaults for the rest), or from different sources — without forcing callers to provide information they don't have.

☕ JavaConstructorOverloading.java
public class Product {

    private final String productId;
    private       String name;
    private       double price;
    private       String category;
    private       int    stockQuantity;
    private       boolean isActive;

    // Constructor 1: Minimum required info
    public Product(String productId, String name, double price) {
        this(productId, name, price, "General"); // Delegates to Constructor 2
    }

    // Constructor 2: With category
    public Product(String productId, String name, double price, String category) {
        this(productId, name, price, category, 0); // Delegates to Constructor 3
    }

    // Constructor 3: With stock
    public Product(String productId, String name, double price,
                   String category, int stockQuantity) {
        this(productId, name, price, category, stockQuantity, true); // Delegates to full
    }

    // Constructor 4: FULL constructor — all others delegate here
    public Product(String productId, String name, double price,
                   String category, int stockQuantity, boolean isActive) {
        if (productId == null || productId.isBlank())
            throw new IllegalArgumentException("Product ID required");
        if (price < 0)
            throw new IllegalArgumentException("Price cannot be negative");

        this.productId     = productId;
        this.name          = name;
        this.price         = price;
        this.category      = category;
        this.stockQuantity = stockQuantity;
        this.isActive      = isActive;
    }

    @Override
    public String toString() {
        return "[" + productId + "] " + name + " | ₹" + price
               + " | " + category + " | Stock: " + stockQuantity
               + " | Active: " + isActive;
    }

    public static void main(String[] args) {

        // Uses Constructor 1 — minimal info, rest defaults
        Product p1 = new Product("PRD-001", "Notebook", 50.0);
        System.out.println(p1);
        // [PRD-001] Notebook | ₹50.0 | General | Stock: 0 | Active: true

        // Uses Constructor 2 — with category
        Product p2 = new Product("PRD-002", "Laptop", 75000.0, "Electronics");
        System.out.println(p2);
        // [PRD-002] Laptop | ₹75000.0 | Electronics | Stock: 0 | Active: true

        // Uses Constructor 4 — full details
        Product p3 = new Product("PRD-003", "Phone", 25000.0, "Electronics", 50, true);
        System.out.println(p3);
        // [PRD-003] Phone | ₹25000.0 | Electronics | Stock: 50 | Active: true
    }
}

this() — Calling Another Constructor in the Same Class

this() is a special constructor call that invokes another constructor in the same class. It is the mechanism behind the constructor delegation pattern — shorter constructors call longer ones with default arguments, keeping all initialization logic in one place. this() must be the very first statement in the constructor body. No code can precede it.

☕ JavaThisConstructorCall.java
public class Rectangle {

    private double length;
    private double width;
    private String color;
    private boolean filled;

    // Constructor 1: Square (length = width)
    public Rectangle(double side) {
        this(side, side);  // Calls Constructor 2
    }

    // Constructor 2: Rectangle with default color
    public Rectangle(double length, double width) {
        this(length, width, "White"); // Calls Constructor 3
    }

    // Constructor 3: Rectangle with color but not filled
    public Rectangle(double length, double width, String color) {
        this(length, width, color, false); // Calls Constructor 4
    }

    // Constructor 4: FULL — all others ultimately call this
    public Rectangle(double length, double width, String color, boolean filled) {
        this.length = length;
        this.width  = width;
        this.color  = color;
        this.filled = filled;
        System.out.println("Rectangle created: " + this);
    }

    @Override
    public String toString() {
        return length + "x" + width + " | " + color + " | filled=" + filled;
    }

    public static void main(String[] args) {

        Rectangle square = new Rectangle(5.0);
        // Rectangle created: 5.0x5.0 | White | filled=false

        Rectangle rect = new Rectangle(8.0, 3.0);
        // Rectangle created: 8.0x3.0 | White | filled=false

        Rectangle colored = new Rectangle(6.0, 4.0, "Blue");
        // Rectangle created: 6.0x4.0 | Blue | filled=false

        Rectangle full = new Rectangle(10.0, 5.0, "Red", true);
        // Rectangle created: 10.0x5.0 | Red | filled=true

        // ❌ this() must be the FIRST statement
        // public Rectangle(double x) {
        //     System.out.println("Before"); // Any code here
        //     this(x, x); // COMPILE ERROR — this() must be first
        // }
    }
}

super() — Calling the Parent Class Constructor

super() is used to explicitly call a constructor of the parent class from a subclass constructor. It must be the first statement in the subclass constructor. If a subclass constructor does not explicitly call super(), Java automatically inserts a no-arg super() call. If the parent class has no no-arg constructor, the compiler reports an error — you must call super(args) explicitly.

☕ JavaSuperConstructorCall.java
// Parent class
class Vehicle {
    protected String brand;
    protected String model;
    protected int    year;

    public Vehicle(String brand, String model, int year) {
        this.brand = brand;
        this.model = model;
        this.year  = year;
        System.out.println("Vehicle constructor: " + brand + " " + model);
    }
}

// Child class
class Car extends Vehicle {
    private int   numberOfDoors;
    private String fuelType;

    public Car(String brand, String model, int year, int doors, String fuel) {
        super(brand, model, year); // ✅ Must be first — calls Vehicle constructor
        this.numberOfDoors = doors;
        this.fuelType      = fuel;
        System.out.println("Car constructor: " + doors + " doors, " + fuel);
    }

    @Override
    public String toString() {
        return year + " " + brand + " " + model
               + " | " + numberOfDoors + "-door | " + fuelType;
    }
}

// Grandchild class
class ElectricCar extends Car {
    private int batteryCapacityKwh;

    public ElectricCar(String brand, String model, int year, int battery) {
        super(brand, model, year, 4, "Electric"); // Calls Car constructor
        this.batteryCapacityKwh = battery;
        System.out.println("ElectricCar constructor: " + battery + " kWh battery");
    }

    @Override
    public String toString() {
        return super.toString() + " | Battery: " + batteryCapacityKwh + " kWh";
    }
}

class SuperConstructorDemo {
    public static void main(String[] args) {
        System.out.println("--- Creating ElectricCar ---");
        ElectricCar tesla = new ElectricCar("Tesla", "Model 3", 2024, 75);
        System.out.println(tesla);
        // Output:
        // --- Creating ElectricCar ---
        // Vehicle constructor: Tesla Model 3
        // Car constructor: 4 doors, Electric
        // ElectricCar constructor: 75 kWh battery
        // 2024 Tesla Model 3 | 4-door | Electric | Battery: 75 kWh
    }
}

Constructor Chaining — this() and super() Together

Constructor chaining is the technique of having constructors call other constructors — either in the same class via this() or in the parent class via super(). It eliminates code duplication, centralizes validation logic, and ensures every object passes through a single 'master' initialization path regardless of which constructor the caller uses.

☕ JavaConstructorChaining.java
// Parent class with overloaded constructors using this()
class Person {
    protected String name;
    protected int    age;
    protected String email;

    // Minimal
    public Person(String name) {
        this(name, 0); // → Person(name, age)
    }

    // Name + age
    public Person(String name, int age) {
        this(name, age, "not-provided@example.com"); // → Person(name, age, email)
    }

    // Full — all this() chains end here
    public Person(String name, int age, String email) {
        this.name  = name;
        this.age   = age;
        this.email = email;
    }
}

// Subclass — uses super() to chain to Person's constructors
class Manager extends Person {
    private String department;
    private int    teamSize;

    // Chains to Person(name, age, email) via super()
    // then sets its own fields
    public Manager(String name, int age, String email,
                   String department, int teamSize) {
        super(name, age, email); // 1. Initialize Person fields
        this.department = department; // 2. Initialize Manager fields
        this.teamSize   = teamSize;
    }

    // Shorter overload — delegates within Manager via this()
    public Manager(String name, String department) {
        this(name, 30, "manager@company.com", department, 5);
        // this() → Manager full constructor → super() → Person full constructor
    }

    @Override
    public String toString() {
        return name + " (" + age + ") | " + department + " | Team: " + teamSize;
    }
}

class ChainingDemo {
    public static void main(String[] args) {

        // Short form — chaining handles the rest
        Manager m1 = new Manager("Sunita", "Engineering");
        System.out.println(m1); // Sunita (30) | Engineering | Team: 5

        // Full form
        Manager m2 = new Manager("Arjun", 38, "arjun@co.com", "Finance", 12);
        System.out.println(m2); // Arjun (38) | Finance | Team: 12

        // Person directly
        Person p = new Person("Ravi");
        System.out.println(p.name + " | " + p.email);
        // Ravi | not-provided@example.com
    }
}

Private Constructor — Controlling Object Creation

A private constructor prevents any code outside the class from creating instances using new. This is a powerful design tool used in three primary patterns: the Singleton Pattern (ensure only one instance), Utility Classes (no instantiation needed — all static methods), and the Factory Method Pattern (controlled object creation through named factory methods).

☕ JavaPrivateConstructor.java
// ✅ PATTERN 1: Singleton — only ONE instance ever
public class ApplicationConfig {

    private static ApplicationConfig instance; // Single instance

    private String dbUrl;
    private String appName;

    // ✅ Private constructor — no one can call new ApplicationConfig()
    private ApplicationConfig() {
        this.dbUrl   = "jdbc:postgresql://localhost:5432/mydb";
        this.appName = "TechSustainify App";
        System.out.println("ApplicationConfig initialized");
    }

    // ✅ Public static method — the only way to get the instance
    public static ApplicationConfig getInstance() {
        if (instance == null) {
            instance = new ApplicationConfig(); // Only created once
        }
        return instance;
    }

    public String getDbUrl()   { return dbUrl; }
    public String getAppName() { return appName; }
}


// ✅ PATTERN 2: Utility class — all static, no instantiation
public final class MathUtils {

    // ✅ Private constructor — prevents accidental instantiation
    private MathUtils() {
        throw new UnsupportedOperationException("MathUtils is a utility class");
    }

    public static int    factorial(int n)     { return n <= 1 ? 1 : n * factorial(n-1); }
    public static boolean isPrime(int n)      { if (n < 2) return false; for (int i=2; i*i<=n; i++) if(n%i==0) return false; return true; }
    public static double circleArea(double r) { return Math.PI * r * r; }
}


// ✅ PATTERN 3: Factory Method — named constructors for clarity
public class Temperature {

    private final double celsius;

    private Temperature(double celsius) {
        this.celsius = celsius;
    }

    // Factory methods — clearly named, more readable than constructors
    public static Temperature fromCelsius(double c)    { return new Temperature(c); }
    public static Temperature fromFahrenheit(double f) { return new Temperature((f - 32) * 5 / 9); }
    public static Temperature fromKelvin(double k)     { return new Temperature(k - 273.15); }

    public double toCelsius()    { return celsius; }
    public double toFahrenheit() { return celsius * 9 / 5 + 32; }
    public double toKelvin()     { return celsius + 273.15; }

    @Override
    public String toString() { return String.format("%.2f°C", celsius); }
}

class PrivateConstructorDemo {
    public static void main(String[] args) {

        // Singleton
        ApplicationConfig cfg1 = ApplicationConfig.getInstance();
        ApplicationConfig cfg2 = ApplicationConfig.getInstance();
        System.out.println(cfg1 == cfg2); // true — same object

        // Utility
        System.out.println(MathUtils.factorial(5)); // 120
        System.out.println(MathUtils.isPrime(17));  // true
        // new MathUtils(); // Would throw UnsupportedOperationException

        // Factory methods
        Temperature t1 = Temperature.fromFahrenheit(98.6);
        Temperature t2 = Temperature.fromKelvin(373.15);
        System.out.println(t1); // 37.00°C
        System.out.println(t2); // 100.00°C
    }
}

Constructor vs Method — Key Differences

Constructors and methods look similar syntactically but serve entirely different purposes. Understanding the precise differences is a frequent interview topic and prevents subtle coding mistakes.

CriteriaConstructorMethod
NameMUST match class name exactlyAny valid identifier (not matching class name)
Return typeNone — not even voidMust have a return type (or void)
InvocationCalled automatically by 'new' keywordCalled explicitly by the programmer
PurposeInitialize a newly created objectDefine behavior — operations on an object
InheritanceNOT inherited by subclassesInherited by subclasses (unless private/final)
OverridingCannot be overridden (not inherited)Can be overridden in subclasses
Overloading✅ Yes — constructor overloading is common✅ Yes — method overloading is common
static/abstract/final❌ Cannot be static, abstract, or final✅ Can be static, abstract, or final
this()/super()✅ Can call this() or super() as first statement❌ Cannot use this()/super() as constructor calls
@OverrideNot applicableApplicable for overriding parent methods
ExecutionOnce — at object creation timeAny number of times during object's lifetime
☕ JavaConstructorVsMethod.java
public class Lamp {

    private String color;
    private boolean isOn;

    // ✅ CONSTRUCTOR — no return type, same name as class
    public Lamp(String color) {
        this.color = color;
        this.isOn  = false;
        System.out.println("Lamp created: " + color);
    }

    // ✅ METHOD — has return type (void), called explicitly
    public void turnOn() {
        isOn = true;
        System.out.println(color + " lamp is ON");
    }

    // ⚠️ TRICKY: This looks like a constructor but has 'void' return type
    // It is a METHOD named Lamp — NOT a constructor
    // The class will use the auto-generated default constructor
    public void Lamp() {
        System.out.println("I am a method, not a constructor!");
    }

    public static void main(String[] args) {
        Lamp l = new Lamp("Red");   // Constructor called
        l.turnOn();                   // Method called
        l.Lamp();                     // Method named Lamp — NOT constructor
        // Output:
        // Lamp created: Red
        // Red lamp is ON
        // I am a method, not a constructor!
    }
}

Constructors & Inheritance — How super() Binds the Hierarchy

Constructors are not inherited in Java — a subclass does not get the parent's constructors. However, every subclass constructor must call a parent constructor — either explicitly using super(args) or implicitly (Java inserts super() automatically). This ensures the parent's fields are always initialized properly before the child adds its own state. The chain always starts from Object at the top.

☕ JavaConstructorInheritance.java
class LivingBeing {
    protected boolean isAlive;

    public LivingBeing() {
        this.isAlive = true;
        System.out.println("1. LivingBeing() — isAlive=" + isAlive);
    }
}

class Animal extends LivingBeing {
    protected String species;

    public Animal(String species) {
        // super() is called implicitly — LivingBeing() runs first
        this.species = species;
        System.out.println("2. Animal(" + species + ")");
    }
}

class Dog extends Animal {
    private String breed;

    public Dog(String breed) {
        super("Canine"); // Explicit super — calls Animal("Canine")
        this.breed = breed;
        System.out.println("3. Dog(" + breed + ")");
    }
}

class GuideDog extends Dog {
    private String ownerName;

    public GuideDog(String breed, String owner) {
        super(breed); // Calls Dog(breed)
        this.ownerName = owner;
        System.out.println("4. GuideDog — owner: " + owner);
    }
}

class InheritanceDemo {
    public static void main(String[] args) {
        System.out.println("Creating GuideDog...");
        GuideDog gd = new GuideDog("Labrador", "Suresh");
        System.out.println("isAlive: " + gd.isAlive + " | species: " + gd.species);
        // Output:
        // Creating GuideDog...
        // 1. LivingBeing() — isAlive=true
        // 2. Animal(Canine)
        // 3. Dog(Labrador)
        // 4. GuideDog — owner: Suresh
        // isAlive: true | species: Canine

        // ⚠️ What if parent has no no-arg constructor?
        // class Parent { public Parent(String x) {} }
        // class Child extends Parent {
        //     public Child() { }  // COMPILE ERROR — implicit super() has no match
        // }
        // Fix: public Child() { super("default"); }
    }
}

Common Mistakes & Pitfalls — Bugs That Fool Everyone

These mistakes appear consistently in beginner Java code involving constructors. Most cause compile errors immediately, but some create subtle runtime bugs that are hard to diagnose.

☕ JavaConstructorMistakes.java
// ❌ MISTAKE 1: Adding a return type to a constructor — makes it a method
class Gadget {
    String name;
    public void Gadget(String name) { // ❌ This is a METHOD, not a constructor!
        this.name = name;
    }
}
// Gadget g = new Gadget("Phone"); // COMPILE ERROR — no matching constructor
// ✅ Fix: remove 'void'
// public Gadget(String name) { this.name = name; }

// ❌ MISTAKE 2: Expecting default constructor after defining a parameterized one
class Config {
    String env;
    public Config(String env) { this.env = env; }
}
// Config c = new Config(); // COMPILE ERROR — no default constructor
// ✅ Fix: add explicit no-arg constructor
// public Config() { this("development"); }

// ❌ MISTAKE 3: this() not the first statement
class Point {
    int x, y;
    public Point(int x) {
        System.out.println("Creating point"); // Any code here
        // this(x, 0); // COMPILE ERROR — call to this must be first statement
    }
    public Point(int x, int y) { this.x = x; this.y = y; }
}

// ❌ MISTAKE 4: Forgetting 'this.' in constructor — field shadowed by parameter
class Book {
    String title;
    int    pages;
    public Book(String title, int pages) {
        title = title; // ❌ Assigns parameter to itself — field unchanged!
        pages = pages; // ❌ Same problem
    }
}
// ✅ Fix: this.title = title; this.pages = pages;

// ❌ MISTAKE 5: Calling overridable method in constructor
abstract class Widget {
    String label;
    public Widget() {
        initialize(); // ❌ Dangerous — calls overridden version in subclass
    }
    public void initialize() { label = "Widget"; }
}
class Button extends Widget {
    String icon = "🔘"; // Set AFTER super() returns
    @Override
    public void initialize() {
        label = "Button: " + icon; // icon is null here — not yet initialized!
    }
}
// new Button() → label = "Button: null" — subtle bug!

// ❌ MISTAKE 6: Recursive constructor chain — compile error
class Circular {
    public Circular()        { this("default"); } // Calls Circular(String)
    public Circular(String s){ this(); }           // Calls Circular() — CIRCULAR!
    // COMPILE ERROR: recursive constructor invocation
}

Bad Practices & Anti-Patterns — What Senior Developers Reject

These constructor anti-patterns are common reasons for failed code reviews and brittle object initialization in professional Java teams.

🚫
Constructor with Too Many Parameters

A constructor with 7, 8, or more parameters — especially of the same type — is fragile and unreadable. It is trivial to swap argument positions silently. Use the Builder pattern for complex objects: new User.Builder().name("Alice").email("a@b.com").role("ADMIN").build(). Java 16+ records are ideal for simple value objects with many fields.

🚫
Doing Heavy Work in Constructors

Constructors should initialize fields and validate inputs — nothing more. Performing database queries, file I/O, network calls, or complex computations in a constructor makes testing difficult (you cannot instantiate the class without all dependencies available), slows object creation, and makes failures hard to attribute. Use factory methods or post-construction initialization (init() method or Spring's @PostConstruct) for heavy work.

🚫
Calling Overridable Methods from Constructors

Calling a non-final method from a constructor is dangerous when subclasses exist. The overriding method in the subclass runs before the subclass fields are initialized (super() runs first), so the subclass method sees uninitialized (null/0) field values. Mark such methods final, or better — move the call out of the constructor entirely.

🚫
Duplicate Logic Across Overloaded Constructors

Having multiple overloaded constructors that each independently set fields — instead of delegating with this() — leads to duplication. When initialization logic changes (e.g., adding a new validation), you must update every constructor. Use the delegation pattern: all constructors chain to one 'master' constructor via this() that contains the actual initialization code.

🚫
Public Constructor on Singleton or Utility Class

A Singleton class with a public constructor undermines the pattern — anyone can call new Singleton() and bypass the single-instance enforcement. Always make Singleton constructors private. Similarly, utility classes (all-static) should have a private constructor that throws UnsupportedOperationException, explicitly preventing instantiation and documenting the intent.

🚫
Not Validating in Constructors of Domain Objects

Creating a domain object (Order, User, BankAccount) without validating constructor arguments allows invalid objects into the system. An Order with a negative amount, a User with a null email, or a BankAccount with an empty account number should be impossible to create. Validate in the constructor using guard clauses and throw IllegalArgumentException for invalid inputs.

☕ JavaConstructorAntiPatterns.java
// ❌ ANTI-PATTERN 1: Too many parameters — easy to mix up
class UserBad {
    public UserBad(String first, String last, String email,
                   String phone, int age, String city,
                   String role, boolean active) {
        // new UserBad("Alice","Smith","alice@x.com","+91...",25,"Delhi","ADMIN",true)
        // — what if you swap first and last? Compiles silently, wrong data!
    }
}
// ✅ BETTER: Builder pattern
class User {
    private final String firstName, lastName, email, phone, city, role;
    private final int age;
    private final boolean active;

    private User(Builder b) {
        this.firstName = b.firstName; this.lastName = b.lastName;
        this.email = b.email;         this.phone = b.phone;
        this.age = b.age;              this.city = b.city;
        this.role = b.role;            this.active = b.active;
    }

    public static class Builder {
        private String firstName, lastName, email, phone, city, role;
        private int age; private boolean active = true;
        public Builder firstName(String v) { this.firstName = v; return this; }
        public Builder lastName(String v)  { this.lastName  = v; return this; }
        public Builder email(String v)     { this.email     = v; return this; }
        public Builder role(String v)      { this.role      = v; return this; }
        public Builder age(int v)          { this.age       = v; return this; }
        public User build() { return new User(this); }
    }
}
// ✅ Usage — readable, safe, order-independent:
// User u = new User.Builder().firstName("Alice").lastName("Smith")
//                           .email("a@x.com").role("ADMIN").age(25).build();

Real-World Production Code Examples — Constructors in Context

The following examples model idiomatic Java constructor usage patterns in real enterprise Spring Boot-style codebases — clean initialization at every layer.

☕ JavaOrderDomain.java — Production-Grade Constructor Patterns
package com.techsustainify.order.domain;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

/**
 * Domain entity with validation in constructor.
 * Immutable fields via final + defensive copying of mutable collections.
 */
public class Order {

    private final String        orderId;
    private final String        customerId;
    private final LocalDateTime createdAt;
    private final List<OrderItem> items;
    private       OrderStatus   status;
    private       double        totalAmount;

    // ✅ Primary constructor — validates all inputs
    public Order(String customerId, List<OrderItem> items) {
        // Validation — fail fast
        Objects.requireNonNull(customerId, "Customer ID must not be null");
        Objects.requireNonNull(items,      "Order items must not be null");
        if (items.isEmpty())
            throw new IllegalArgumentException("Order must have at least one item");

        this.orderId    = "ORD-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
        this.customerId = customerId;
        this.createdAt  = LocalDateTime.now();
        this.items      = Collections.unmodifiableList(new ArrayList<>(items)); // Defensive copy
        this.status     = OrderStatus.PENDING;
        this.totalAmount = items.stream().mapToDouble(OrderItem::getSubtotal).sum();
    }

    // Copy constructor — for creating a modified version of an existing order
    public Order(Order source, OrderStatus newStatus) {
        this.orderId     = source.orderId;
        this.customerId  = source.customerId;
        this.createdAt   = source.createdAt;
        this.items       = source.items; // Unmodifiable — safe to share
        this.status      = newStatus;
        this.totalAmount = source.totalAmount;
    }

    public String        getOrderId()     { return orderId; }
    public String        getCustomerId()  { return customerId; }
    public OrderStatus   getStatus()      { return status; }
    public double        getTotalAmount() { return totalAmount; }
    public List<OrderItem> getItems()     { return items; }

    @Override
    public String toString() {
        return orderId + " | " + customerId + " | " + status
               + " | ₹" + String.format("%.2f", totalAmount)
               + " | Items: " + items.size();
    }
}

// ✅ Value object — minimal, immutable, validated
class OrderItem {
    private final String productId;
    private final String productName;
    private final int    quantity;
    private final double unitPrice;

    public OrderItem(String productId, String productName, int qty, double unitPrice) {
        if (qty <= 0)        throw new IllegalArgumentException("Quantity must be > 0");
        if (unitPrice <= 0)  throw new IllegalArgumentException("Unit price must be > 0");

        this.productId   = Objects.requireNonNull(productId,   "Product ID required");
        this.productName = Objects.requireNonNull(productName, "Product name required");
        this.quantity    = qty;
        this.unitPrice   = unitPrice;
    }

    public double getSubtotal() { return quantity * unitPrice; }

    @Override
    public String toString() {
        return productName + " x" + quantity + " @ ₹" + unitPrice;
    }
}

enum OrderStatus { PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED }

class OrderDemo {
    public static void main(String[] args) {
        List<OrderItem> items = List.of(
            new OrderItem("P001", "Laptop", 1, 75000.0),
            new OrderItem("P002", "Mouse",  2, 750.0)
        );
        Order order = new Order("CUST-42", items);
        System.out.println(order);
        // ORD-XXXXXXXX | CUST-42 | PENDING | ₹76500.00 | Items: 2

        // Status update via copy constructor
        Order confirmed = new Order(order, OrderStatus.CONFIRMED);
        System.out.println(confirmed);
        // Same order ID, same amount, status=CONFIRMED
    }
}
☕ JavaJDBCConnectionPool.java — Singleton with Private Constructor
package com.techsustainify.db;

/**
 * Thread-safe Singleton connection pool.
 * Private constructor + static factory method pattern.
 */
public class ConnectionPool {

    private static volatile ConnectionPool instance; // volatile for thread visibility

    private final String url;
    private final String username;
    private final int    maxConnections;
    private       int    activeConnections;

    // ✅ Private constructor — only called once from getInstance()
    private ConnectionPool(String url, String username, int maxConnections) {
        Objects.requireNonNull(url,      "DB URL required");
        Objects.requireNonNull(username, "DB username required");
        if (maxConnections <= 0)
            throw new IllegalArgumentException("Max connections must be > 0");

        this.url              = url;
        this.username         = username;
        this.maxConnections   = maxConnections;
        this.activeConnections = 0;
        System.out.println("Connection pool initialized: " + url
                           + " (max=" + maxConnections + ")");
    }

    // ✅ Thread-safe double-checked locking Singleton
    public static ConnectionPool getInstance(String url, String user, int max) {
        if (instance == null) {
            synchronized (ConnectionPool.class) {
                if (instance == null) {
                    instance = new ConnectionPool(url, user, max);
                }
            }
        }
        return instance;
    }

    public synchronized String getConnection() {
        if (activeConnections >= maxConnections) {
            throw new RuntimeException("Connection pool exhausted");
        }
        activeConnections++;
        return "Connection-" + activeConnections + " to " + url;
    }

    public synchronized void releaseConnection() {
        if (activeConnections > 0) activeConnections--;
    }

    public int getActiveConnections() { return activeConnections; }

    public static void main(String[] args) {
        ConnectionPool pool1 = ConnectionPool.getInstance(
            "jdbc:postgresql://localhost:5432/mydb", "admin", 10);
        ConnectionPool pool2 = ConnectionPool.getInstance(
            "jdbc:mysql://other:3306/db", "root", 5); // Same instance returned

        System.out.println(pool1 == pool2); // true — Singleton

        String conn = pool1.getConnection();
        System.out.println("Got: " + conn);
        System.out.println("Active: " + pool1.getActiveConnections()); // 1
        pool1.releaseConnection();
        System.out.println("Active: " + pool1.getActiveConnections()); // 0
    }
}

Constructor Chaining Flowchart — Execution Order

This flowchart shows the constructor execution order when a concrete subclass is instantiated in a multi-level inheritance hierarchy, and how this() and super() calls chain together.

▶ new ConcreteClass(args)Object creation triggered
JVM invokes
⚙️ ConcreteClass constructorCalls this() or super() first
Check first statement
🔍 this() call present?Delegates to another constructor in same class
YES — this() call
⚙️ Target constructor in classMay also call super()
Reaches super()
⚙️ super() — Parent constructorExplicit or implicit call
Go up hierarchy
🔍 More parents in hierarchy?Walk up until Object is reached
YES — another parent
⚙️ Object() constructorRoot of all Java hierarchies
Object() completes
⬇️ Return down the chainExecute body of each constructor top-to-bottom
All constructors done
✅ Object fully initializedAll fields set; object ready to use

Code Execution Flow — from source to output

Java Constructor Interview Questions — Beginner to Advanced

These questions are consistently asked in Java fresher and experienced developer interviews, OCPJP certification exams, and campus placement tests.

Practice Questions — Test Your Constructor Knowledge

Challenge yourself with these practice questions. Attempt each independently before reading the answer — active recall is proven to be 2–3x more effective than passive reading.

1. What is the output? class A { A() { System.out.println("A no-arg"); } A(int x) { System.out.println("A int: " + x); } } class B extends A { B() { System.out.println("B no-arg"); } B(int x) { super(x); System.out.println("B int: " + x); } } public class Test { public static void main(String[] args) { new B(); System.out.println("---"); new B(5); } }

Easy

2. Will this compile? What is the issue if any? class Config { String env; int port; public Config(String env) { this.env = env; } } class AppConfig extends Config { String appName; public AppConfig(String appName) { this.appName = appName; } }

Easy

3. Identify the bug and fix it: class Counter { int count; public Counter(int count) { count = count; } public int getCount() { return count; } } Counter c = new Counter(10); System.out.println(c.getCount()); // prints 0, not 10

Easy

4. Write a class 'Circle' with: a private double radius, a no-arg constructor (radius = 1.0), a parameterized constructor (takes radius with validation: must be > 0), and a copy constructor. Include a getArea() method.

Medium

5. What is the output? Explain the this() chain. class Pizza { String size, crust, topping; Pizza(String size) { this(size, "Thin"); System.out.println("1-arg done"); } Pizza(String size, String crust) { this(size, crust, "Cheese"); System.out.println("2-arg done"); } Pizza(String size, String crust, String topping) { this.size = size; this.crust = crust; this.topping = topping; System.out.println("3-arg done: " + size + ", " + crust + ", " + topping); } } new Pizza("Large");

Medium

6. Implement the Singleton pattern for a Logger class with: a private static instance, a private constructor that sets a log file name, a public static getInstance() method, and a public log(String message) method.

Medium

7. What is the output? Why? abstract class Shape { String type; Shape() { type = getType(); // Calls overridden method! System.out.println("Shape type: " + type); } abstract String getType(); } class Triangle extends Shape { int sides = 3; Triangle() { super(); System.out.println("Triangle sides: " + sides); } @Override String getType() { return "Triangle with " + sides + " sides"; } } new Triangle();

Hard

8. Create a class 'ImmutablePoint' representing a 2D point that: cannot have its x, y changed after creation, supports creation from Cartesian (x, y) or Polar (radius, angle in radians) coordinates using private constructors and static factory methods, and implements toString().

Hard

Conclusion — Constructors: The Birth Certificate of Every Java Object

Constructors are the entry point of every object's life. They establish the initial state, enforce invariants, and determine the valid ways an object can be created. A well-designed constructor ensures that an object is always valid from the moment it exists — no partial initialization, no null fields that shouldn't be null, no negative values where only positive are meaningful.

The difference between junior and senior Java code is visible in constructor design. Junior code has constructors that do too much (database calls, file I/O), too little (no validation, public setters for everything), or too many variations (duplicated logic instead of this() delegation). Senior code has lean, validating constructors that delegate through this() chains, use the Builder pattern for complex objects, private constructors for Singletons and utilities, and copy constructors for safe object duplication.

Constructor TypePurposeWhen to Use
Default (auto-generated)No-arg, empty body — allows new ClassName()When no initialization logic is needed
No-arg (explicit)Sets sensible defaults; required by frameworksFrameworks (Hibernate, Jackson); default config
ParameterizedInitializes object with provided values + validationPrimary way to create well-defined domain objects
Copy constructorCreates independent copy (deep copy) of an objectWhen you need isolated copies of mutable objects
Constructor overloadingMultiple creation paths — shorter ones delegateOptional parameters; simulating default args
this() callDelegates to another constructor in same classEliminate duplicate init code; delegation pattern
super() callCalls parent class constructorEvery subclass must initialize parent's fields
Private constructorPrevent external instantiationSingleton, utility classes, Factory Method Pattern

Your next step: Java Method Parameters — where you'll explore how data flows into methods (and constructors) through parameters, pass-by-value semantics for primitives and objects, varargs, and parameter design best practices. ☕

Frequently Asked Questions — Java Constructor