Java Constructors Explained — How Objects Get Built from Scratch
Every app you have ever used — a banking app, a game, a chat tool — is built from objects. A bank account object holds a balance. A user object holds a name and email. But here's the question nobody asks on day one: who sets those values up when the object is first created? Who makes sure a new bank account starts with a valid account number instead of garbage data? That's exactly the job of a constructor.
Before constructors existed as a concept, developers had to manually call setup methods after creating an object, which meant forgetting to call them was a silent bug waiting to explode. Constructors solved that by guaranteeing initialization code runs at the exact moment of object creation — it's impossible to create the object without the constructor firing. That guarantee is what makes Java programs reliable.
By the end of this article you'll know what a constructor is, why Java gives you a free one you never asked for, how to write your own with custom parameters, how to chain constructors together to avoid repeating yourself, and the exact mistakes that trip up beginners in interviews and on the job.
What a Constructor Is and Why Java Can't Live Without One
A constructor is a special block of code inside a class that runs automatically every single time you create a new object from that class. It looks almost like a method, but with two important differences: it has the exact same name as the class, and it has no return type — not even void.
Why no return type? Because the constructor's only job is to initialize the object that's already being built. Java handles the memory allocation and returns the reference for you. Your job is just to fill in the starting values.
Here's the mental model that makes this stick: imagine a cookie cutter (the class) and a cookie (the object). The cookie cutter defines the shape. Every time you press it into dough, you get a new cookie. The constructor is the act of pressing — it's what brings the cookie into existence with its initial form. You don't call the constructor manually after creating the object. Writing new BankAccount() is calling the constructor. They're the same thing.
Every class in Java has at least one constructor. If you don't write one, Java quietly provides a default no-argument constructor behind the scenes. The moment you write your own constructor, Java stops providing that free one — and that's one of the most common traps beginners fall into.
// A simple BankAccount class that demonstrates the most basic constructor public class BankAccount { // These are the fields — the data an account holds String accountHolder; double balance; String accountNumber; // This is the constructor — same name as the class, no return type // It runs automatically the moment someone writes: new BankAccount(...) public BankAccount(String holderName, String accNumber, double openingBalance) { accountHolder = holderName; // Set the holder's name right away accountNumber = accNumber; // Assign the account number balance = openingBalance; // Start with the opening deposit System.out.println("Account created for: " + accountHolder); System.out.println("Account Number: " + accountNumber); System.out.println("Opening Balance: $" + balance); System.out.println("------------------------------"); } public static void main(String[] args) { // Creating two BankAccount objects — constructor fires for each one BankAccount firstAccount = new BankAccount("Alice Johnson", "ACC-1001", 500.00); BankAccount secondAccount = new BankAccount("Bob Martinez", "ACC-1002", 1200.50); // Both objects are now fully initialized — no extra setup needed System.out.println(firstAccount.accountHolder + " has $" + firstAccount.balance); System.out.println(secondAccount.accountHolder + " has $" + secondAccount.balance); } }
Account Number: ACC-1001
Opening Balance: $500.0
------------------------------
Account created for: Bob Martinez
Account Number: ACC-1002
Opening Balance: $1200.5
------------------------------
Alice Johnson has $500.0
Bob Martinez has $1200.5
The Three Flavours of Constructors — Default, Parameterized, and Copy
Java gives you three kinds of constructors to work with, each solving a slightly different problem. Understanding when to use each one is what separates developers who guess from developers who design.
Default Constructor — This is a no-argument constructor. You either write one yourself or Java generates one invisibly. It's useful when you have a valid 'empty' starting state — like a shopping cart that starts empty. Java's auto-generated default constructor sets numeric fields to 0, booleans to false, and objects to null.
Parameterized Constructor — This takes arguments so you can customize the object at birth. A Car that knows its make, model and year from the moment it's created is safer than a Car with empty fields you might forget to fill. This is the most common type you'll write.
Copy Constructor — This creates a new object as an exact duplicate of an existing object. Java doesn't provide this automatically — you write it by accepting another object of the same type as the parameter. It's critical when you want a true independent copy rather than two variables pointing at the same object in memory.
All three can coexist in the same class — that's called constructor overloading, and it lets you create objects in multiple valid ways.
public class Laptop { String brand; String model; int ramGigabytes; double priceUSD; // 1. DEFAULT CONSTRUCTOR — no parameters, sensible starting state // Useful when you want to build up a Laptop piece by piece public Laptop() { brand = "Unknown"; model = "Unknown"; ramGigabytes = 8; // A reasonable default — 8GB is standard priceUSD = 0.0; System.out.println("[Default Constructor] Generic laptop created."); } // 2. PARAMETERIZED CONSTRUCTOR — caller provides everything upfront // This is the most common pattern you'll write in real projects public Laptop(String brand, String model, int ramGigabytes, double priceUSD) { this.brand = brand; // 'this' distinguishes field from parameter this.model = model; this.ramGigabytes = ramGigabytes; this.priceUSD = priceUSD; System.out.println("[Parameterized Constructor] " + brand + " " + model + " created."); } // 3. COPY CONSTRUCTOR — creates an independent clone of an existing Laptop // Changing the copy won't affect the original — they're separate objects public Laptop(Laptop existingLaptop) { this.brand = existingLaptop.brand; this.model = existingLaptop.model; this.ramGigabytes = existingLaptop.ramGigabytes; this.priceUSD = existingLaptop.priceUSD; System.out.println("[Copy Constructor] Copied " + this.brand + " " + this.model + "."); } // Helper to display laptop info cleanly public void printDetails() { System.out.println(" Brand: " + brand + " | Model: " + model + " | RAM: " + ramGigabytes + "GB | Price: $" + priceUSD); } public static void main(String[] args) { System.out.println("--- Using Default Constructor ---"); Laptop genericLaptop = new Laptop(); genericLaptop.printDetails(); System.out.println(); System.out.println("--- Using Parameterized Constructor ---"); Laptop devLaptop = new Laptop("Dell", "XPS 15", 32, 1899.99); devLaptop.printDetails(); System.out.println(); System.out.println("--- Using Copy Constructor ---"); Laptop backupLaptop = new Laptop(devLaptop); // A brand new independent object backupLaptop.priceUSD = 1799.99; // Modify the copy — original untouched System.out.println(" Original:"); devLaptop.printDetails(); System.out.println(" Copy (with different price):"); backupLaptop.printDetails(); } }
[Default Constructor] Generic laptop created.
Brand: Unknown | Model: Unknown | RAM: 8GB | Price: $0.0
--- Using Parameterized Constructor ---
[Parameterized Constructor] Dell XPS 15 created.
Brand: Dell | Model: XPS 15 | RAM: 32GB | Price: $1899.99
--- Using Copy Constructor ---
[Copy Constructor] Copied Dell XPS 15.
Original:
Brand: Dell | Model: XPS 15 | RAM: 32GB | Price: $1899.99
Copy (with different price):
Brand: Dell | Model: XPS 15 | RAM: 32GB | Price: $1799.99
Constructor Chaining with this() — Stop Copy-Pasting Initialization Code
Once you have multiple constructors, you'll notice something ugly: initialization logic starts repeating itself. Imagine a Pizza class where every constructor sets a default size and crust type before doing anything else. Copy-pasting that logic into three constructors is a maintenance nightmare — change one, forget the others.
Constructor chaining solves this. Using this() as the very first line of a constructor, you can call another constructor in the same class. One constructor does all the real work; the others just fill in defaults and delegate to it. This is the Don't Repeat Yourself principle applied to constructors.
The rule is strict: this() must be the absolute first statement in the constructor body. Java enforces this because you can't partially initialize an object before handing control to another constructor — that would leave the object in a broken half-built state.
This pattern is everywhere in professional Java code. The Spring framework, Android's View system, and virtually every well-designed library uses constructor chaining internally. Learning it now means you'll recognize it when you read production code.
public class Pizza { String size; // "Small", "Medium", "Large" String crustType; // "Thin", "Thick", "Stuffed" String topping; // Main topping boolean isVegetarian; // MASTER CONSTRUCTOR — all the real initialization happens here // All other constructors will eventually call this one public Pizza(String size, String crustType, String topping, boolean isVegetarian) { this.size = size; this.crustType = crustType; this.topping = topping; this.isVegetarian = isVegetarian; System.out.println("Pizza initialized: " + size + " | " + crustType + " crust | " + topping + " | Veg: " + isVegetarian); } // CONVENIENCE CONSTRUCTOR — caller just picks size and topping // Chains to the master constructor with sensible defaults public Pizza(String size, String topping) { this(size, "Thin", topping, false); // Calls the 4-arg constructor above System.out.println("(Used size+topping shortcut — defaulted to Thin crust, non-veg)"); } // MINIMAL CONSTRUCTOR — caller just wants a plain default pizza // Chains to the 2-arg constructor, which chains to the master public Pizza() { this("Medium", "Margherita"); // Calls the 2-arg constructor above System.out.println("(Used no-arg shortcut — got a default Medium Margherita)"); } public void describe() { System.out.println("Your pizza: " + size + " | " + crustType + " crust | " + topping + " | Vegetarian: " + isVegetarian); } public static void main(String[] args) { System.out.println("=== Full custom pizza ==="); Pizza customPizza = new Pizza("Large", "Stuffed", "BBQ Chicken", false); customPizza.describe(); System.out.println(); System.out.println("=== Size and topping only ==="); Pizza quickPizza = new Pizza("Large", "Pepperoni"); quickPizza.describe(); System.out.println(); System.out.println("=== Default pizza ==="); Pizza defaultPizza = new Pizza(); defaultPizza.describe(); } }
Pizza initialized: Large | Stuffed crust | BBQ Chicken | Veg: false
Your pizza: Large | Stuffed crust | BBQ Chicken | Vegetarian: false
=== Size and topping only ===
Pizza initialized: Large | Thin crust | Pepperoni | Veg: false
(Used size+topping shortcut — defaulted to Thin crust, non-veg)
Your pizza: Large | Thin crust | Pepperoni | Vegetarian: false
=== Default pizza ===
Pizza initialized: Medium | Thin crust | Margherita | Veg: false
(Used size+topping shortcut — defaulted to Thin crust, non-veg)
(Used no-arg shortcut — got a default Medium Margherita)
Your pizza: Medium | Thin crust | Margherita | Vegetarian: false
Common Mistakes, Gotchas, and Interview Traps
This is where most articles stop — right before the part that actually saves you in an interview or a code review. Let's walk through the real traps.
The disappearing default constructor — The moment you write a parameterized constructor, Java removes the invisible default constructor it was silently providing. If any code elsewhere creates your object with new YourClass(), it now breaks with a compile error. The fix is simple: explicitly write a no-arg constructor yourself whenever you also have parameterized ones.
Constructors are not inherited — If Animal has a constructor that takes a name, its subclass Dog does not automatically get that constructor. You must explicitly define constructors in Dog and use super(name) to call the parent's. This confuses developers who expect inheritance to cover constructors the same way it covers methods.
Calling overridable methods from a constructor — This one is subtle and dangerous. If you call a method inside a constructor and a subclass overrides that method, the subclass version runs before the subclass constructor has finished. The subclass fields are still at default values (null, 0). The result is logic running on half-built state. Stick to private or final methods inside constructors.
// Demonstrates the three most important constructor traps and their fixes public class ConstructorGotchas { // ============================================================ // GOTCHA 1: Adding a parameterized constructor kills the default one // ============================================================ static class Vehicle { String type; int year; // As soon as we write this, Java's free no-arg constructor disappears public Vehicle(String type, int year) { this.type = type; this.year = year; } // FIX: explicitly provide the no-arg constructor if you still need it public Vehicle() { this("Unknown", 2024); // Chain to the parameterized one — DRY principle } } // ============================================================ // GOTCHA 2: Constructors are NOT inherited by subclasses // ============================================================ static class Animal { String name; public Animal(String name) { this.name = name; System.out.println("Animal constructor: name set to " + name); } } static class Dog extends Animal { String breed; // FIX: Dog must define its OWN constructor and explicitly call super() // Without this, Java would complain there's no no-arg Animal constructor public Dog(String name, String breed) { super(name); // Must be first line — calls Animal's constructor this.breed = breed; System.out.println("Dog constructor: breed set to " + breed); } } // ============================================================ // GOTCHA 3: Naming the constructor wrong (typo = it becomes a method) // ============================================================ static class Counter { int count; // This looks like a constructor but the name is wrong — it's a method! // Java won't error. It just silently becomes a regular void method. // 'count' never gets initialized from here. // public counter() { this.count = 0; } <-- WRONG — lowercase 'c' // CORRECT: constructor name must exactly match the class name, including case public Counter() { this.count = 0; System.out.println("Counter initialized to " + count); } } public static void main(String[] args) { System.out.println("--- Gotcha 1: Default constructor after adding parameterized ---"); Vehicle car = new Vehicle("Sedan", 2022); // Parameterized — works Vehicle emptyVehicle = new Vehicle(); // No-arg — also works because we fixed it System.out.println("Car type: " + car.type + ", Empty type: " + emptyVehicle.type); System.out.println(); System.out.println("--- Gotcha 2: Inheritance and constructors ---"); Dog myDog = new Dog("Rex", "German Shepherd"); // Both constructors fire in order System.out.println("Dog's name from Animal: " + myDog.name); System.out.println(); System.out.println("--- Gotcha 3: Correct constructor naming ---"); Counter visitCounter = new Counter(); System.out.println("Visit count starts at: " + visitCounter.count); } }
Car type: Sedan, Empty type: Unknown
--- Gotcha 2: Inheritance and constructors ---
Animal constructor: name set to Rex
Dog constructor: breed set to German Shepherd
Dog's name from Animal: Rex
--- Gotcha 3: Correct constructor naming ---
Counter initialized to 0
Visit count starts at: 0
| Feature | Default Constructor | Parameterized Constructor | Copy Constructor |
|---|---|---|---|
| Arguments taken | None | One or more (caller-defined) | One object of the same class |
| Provided by Java automatically? | Yes, if no other constructor exists | No — you write it | No — you write it |
| When to use | Object needs a valid 'empty' state | Object must be customized at creation | You need an independent duplicate object |
| Initialization control | Low — Java assigns zero/null defaults | High — caller sets every value | Full — fields copied from source object |
| Risk of null fields | High if you forget to set fields later | Low — forced at creation time | Low — mirrors a known-good object |
| Common real-world example | JavaBeans, serialized objects | Most domain models (User, Order, Product) | Defensive copying in APIs |
🎯 Key Takeaways
- A constructor runs automatically at the exact moment of object creation — you never call it separately — which guarantees your object is always in a valid, initialized state from birth.
- Java gives you a free no-arg constructor only when you write zero constructors yourself; the instant you add a parameterized constructor, that freebie disappears and you must provide the no-arg one explicitly if you still need it.
- Use 'this.fieldName = parameterName' whenever a constructor parameter has the same name as a field — skipping 'this' causes the field to silently stay at its default value with no compiler warning.
- Constructor chaining with this() lets multiple constructors share a single source of initialization logic — one master constructor does the real work and others delegate to it, keeping your code DRY and easy to maintain.
⚠ Common Mistakes to Avoid
- ✕Mistake 1: Writing a parameterized constructor and then calling new MyClass() elsewhere — Symptom: compile error 'constructor MyClass() is undefined' — Fix: explicitly add a no-arg constructor to your class; Java only provides the free one when you have zero constructors of your own.
- ✕Mistake 2: Using the same name for a parameter and a field without 'this' — Symptom: fields silently stay at null or 0 even though you passed values in — Fix: always use 'this.fieldName = parameterName' when the names match, so Java knows the left side is the field and the right is the incoming argument.
- ✕Mistake 3: Putting this() or super() anywhere other than the very first line of a constructor — Symptom: compile error 'call to this/super must be first statement in constructor' — Fix: restructure so this() or super() is unconditionally the first line; move any pre-call logic into the constructor being delegated to instead.
Interview Questions on This Topic
- QWhat is the difference between a constructor and a method in Java? Can a constructor have a return type?
- QIf a parent class only has a parameterized constructor and no default constructor, what happens when you write a subclass without calling super() explicitly — and how do you fix it?
- QCan you have a private constructor? Why would you want one, and what design pattern does it enable?
Frequently Asked Questions
What is a constructor in Java and when does it run?
A constructor is a special block of code inside a class that initializes a newly created object. It runs automatically the moment you use the 'new' keyword to create an object — you never call it manually. Its job is to set the object's fields to their starting values so the object is ready to use immediately.
What is the difference between a constructor and a regular method in Java?
Three key differences: a constructor has exactly the same name as the class, a constructor has no return type (not even void), and a constructor is called automatically when an object is created rather than explicitly by your code. A method can be named anything, must declare a return type, and is called explicitly whenever you need it.
If I don't write a constructor, does my Java class still work?
Yes — Java silently inserts a default no-argument constructor for you whenever you write a class with zero constructors of your own. This auto-generated constructor sets numeric fields to 0, booleans to false, and object references to null. The moment you write any constructor yourself, Java stops providing this freebie.
Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.