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.
accessModifier ClassName(parameters) { // initialization code } Example: public Student(String name, int age) { this.name = name; this.age = age; }
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.
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.
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.
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'.
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.
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.
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.
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.
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.
// â
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.
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.
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.
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.
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.
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.
// 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.
// 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).
// â
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.
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.
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.
// â 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.
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.
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 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.
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.
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.
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.
// â 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.
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
}
}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.
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); } }
Easy2. 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; } }
Easy3. 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
Easy4. 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.
Medium5. 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");
Medium6. 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.
Medium7. 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();
Hard8. 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().
HardConclusion â 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.
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. â