A Java constructor is a block of code with the same name as its class and no return type, which runs when we create an object with new and sets up the object’s initial state. A class can have several constructors with different parameters, and one constructor can call another.
We use constructors to receive required values, validate them and fill final fields, so that no other code ever sees a half-built object. The following example declares a reservation class for a restaurant app with a parameterized constructor and a shorter one that delegates to it.
class Reservation {
private final String guest;
private final int partySize;
Reservation(String guest, int partySize) { // parameterized constructor
if (partySize < 1) {
throw new IllegalArgumentException("partySize must be positive");
}
this.guest = guest;
this.partySize = partySize;
}
Reservation(String guest) {
this(guest, 2); // chains to the constructor above
}
String guest() { return guest; }
int partySize() { return partySize; }
}
The validation lives in one constructor, and the shorter one reuses it through this(…). In the sections that follow, we work through the rules for declaring constructors, default and no-argument constructors, chaining with this() and super(), the Java 25 change that allows statements before super(), record constructors, copy constructors and private constructors.
1. Rules for Declaring a Constructor
When we write new Reservation(“Lokesh”, 4), the JVM allocates memory on the heap, sets every field to its default value (0, false or null), and runs the matching constructor. The constructor can read and assign fields like a method, but the language treats it as a separate kind of member with its own rules.
- The name must match the class name, including letter case.
- There is no return type, not even void. A return; without a value is allowed to leave early, but a constructor never returns a value.
- Constructors can be overloaded with different parameter lists, and they can take any access modifier.
- A constructor cannot be static, final, abstract or synchronized, and it is not inherited by subclasses.
- Every constructor except the one in Object starts the object with a call to another constructor, either this(…) or super(…), written by us or added by the compiler.
The return-type rule hides a common bug. A method named like the class with void in front compiles fine, but it is an ordinary method, so new never calls it.
class DiningTable {
int seats = 4;
void DiningTable() { // a method, not a constructor
seats = 8;
}
}
int seats = new DiningTable().seats; // 4, the "constructor" never ran
2. Default Constructor vs No-Argument Constructor
If a class declares no constructor at all, the compiler adds a default constructor. It takes no parameters, has the same access level as the class, and only calls super(). The compiler inserts it at compile time, so it exists in the class file, and the JVM plays no part in creating it.
class Menu {
String title = "Dinner";
}
Menu menu = new Menu();
String title = menu.title; // "Dinner"
A constructor we write ourselves with no parameters is a no-argument constructor, not a default constructor, even if its body is empty. The difference matters as soon as we add any constructor, because the compiler stops generating the default one.
// does not compile: no suitable constructor found for Reservation(no arguments)
Reservation walkIn = new Reservation();
Frameworks rely on the no-argument form. Jakarta Persistence requires every entity to have a public or protected no-argument constructor, because Hibernate creates the object first and fills the fields from the database row afterward. When a JPA entity gets a convenience constructor, we add a protected no-argument one next to it.
3. Parameterized Constructors and Overloading
A parameterized constructor receives the values an object needs and rejects bad ones before the object exists. Overloading gives callers shorter versions for common cases, and each short version should delegate to the full one so the checks run once.
Reservation dinner = new Reservation("Lokesh", 4);
int dinnerSize = dinner.partySize(); // 4
Reservation couple = new Reservation("Alex");
int coupleSize = couple.partySize(); // 2
Reservation empty = new Reservation("Sam", 0); // IllegalArgumentException: partySize must be positive
Spring uses the same idea for dependency injection. When a bean class has one constructor, Spring calls it with the required beans as arguments, so the fields can be final and a test can create the class with new and fake dependencies. Spring autowiring covers the details. Once a constructor needs more than four or five parameters, a builder is easier to read.
4. Constructor Chaining with this() and super()
Constructor chaining means one constructor calls another before it does its own work. A call to this(…) goes to another constructor of the same class, and a call to super(…) goes to a constructor of the parent class. If a constructor contains neither, the compiler inserts super().
The following example is a parent class for bookings and an event booking that chains through both keywords. Each constructor records its name, so we can see the order.
class TableBooking {
final List<String> steps = new ArrayList<>();
TableBooking(String guest) {
steps.add("TableBooking(guest)");
}
}
class EventBooking extends TableBooking {
EventBooking(String guest, String occasion) {
super(guest);
steps.add("EventBooking(guest, occasion)");
}
EventBooking(String guest) {
this(guest, "birthday");
steps.add("EventBooking(guest)");
}
}
List<String> order = new EventBooking("Mia").steps; // [TableBooking(guest), EventBooking(guest, occasion), EventBooking(guest)]
We called the one-argument constructor, yet its body ran last. The call travels up the chain first, so the parent constructor finishes before any subclass code that depends on it. Field initializers such as steps run right after the super(…) call returns, as described in the guide to the instance initializer block.
A constructor can contain only one of the two calls. When the parent has no no-argument constructor, every subclass constructor must call super(…) with arguments, otherwise the compiler-inserted super() fails.
// does not compile: constructor TableBooking in class TableBooking cannot be applied to given types
class WalkInBooking extends TableBooking { }
5. Statements Before super() in Java 25
Until Java 24, this(…) or super(…) had to be the first statement. Since Java 25, JEP 513 (Flexible Constructor Bodies) is a final feature, so statements may come before the call. The code before the call is the prologue, and the code after it is the epilogue.

The prologue cannot use the object under construction. It cannot read fields, call instance methods or pass this anywhere, with one exception, which is assigning a field of the same class that has no initializer. A return statement is not allowed in the prologue, while throwing an exception is.
class GroupReservation extends Reservation {
private final String menu;
GroupReservation(String guest, int partySize, String menu) {
if (partySize < 8) {
throw new IllegalArgumentException("groups start at 8 guests");
}
this.menu = menu; // allowed: field of this class, no initializer
super(guest, partySize);
}
String menu() { return menu; }
}
GroupReservation party = new GroupReservation("Lokesh", 10, "set menu");
String partyMenu = party.menu(); // "set menu"
GroupReservation small = new GroupReservation("Ana", 3, "a la carte"); // IllegalArgumentException: groups start at 8 guests
Validating arguments before super(…) fails fast, and assigning subclass fields before it protects overridden methods that a parent constructor calls. In the following example, the parent constructor calls describe(), which the subclass overrides.
class Notice {
final String text;
Notice() {
text = describe();
}
String describe() { return "table booked"; }
}
class PartyNotice extends Notice {
private final String occasion;
PartyNotice(String occasion) {
this.occasion = occasion; // set before Notice() calls describe()
super();
}
@Override
String describe() { return "party booked: " + occasion; }
}
String noticeText = new PartyNotice("birthday").text; // "party booked: birthday"
With the assignment placed after super(), the same code returns party booked: null, because occasion is still unset when the parent runs describe(). Reading a field in the prologue, as in System.out.println(this.occasion), fails with cannot reference this before supertype constructor has been called. The Java 25 features overview lists the other changes in this release.
6. Canonical and Compact Constructors in Records
A record gets a canonical constructor from the compiler, with one parameter per component in declaration order. To validate or normalize the values, we write a compact canonical constructor, which has no parameter list and assigns the fields after its body runs.
record Seat(int row, int number) {
Seat { // compact canonical constructor
if (row < 1 || number < 1) {
throw new IllegalArgumentException("row and number start at 1");
}
}
Seat(String code) { // non-canonical constructor
int row = code.charAt(0) - 'A' + 1;
int number = Integer.parseInt(code.substring(1));
this(row, number); // statements before this(), Java 25
}
}
Seat window = new Seat(1, 4); // Seat[row=1, number=4]
Seat fromCode = new Seat("C7"); // Seat[row=3, number=7]
Seat invalid = new Seat(0, 2); // IllegalArgumentException: row and number start at 1
Every non-canonical record constructor must delegate to another constructor with this(…), so all objects pass through the compact check. Inside a compact constructor, assigning a parameter changes the value stored in the field, which is the standard way to normalize input.
record GuestName(String value) {
GuestName {
value = value.strip();
}
}
String cleaned = new GuestName(" Lokesh ").value(); // "Lokesh"
7. Copy Constructors
Java has no built-in copy constructor, but a constructor that takes an object of the same class is a common way to copy one. The copy should duplicate mutable fields, such as lists, otherwise both objects share and change the same list.
class TablePlan {
private final List<String> guests;
TablePlan(List<String> guests) {
this.guests = new ArrayList<>(guests);
}
TablePlan(TablePlan other) { // copy constructor
this(other.guests);
}
void add(String guest) { guests.add(guest); }
int size() { return guests.size(); }
}
TablePlan original = new TablePlan(List.of("Ana", "Ben"));
TablePlan copy = new TablePlan(original);
copy.add("Cy");
int originalSize = original.size(); // 2
int copySize = copy.size(); // 3
A copy constructor is easier to get right than clone(), because it works with final fields and needs no casts or checked exceptions, as compared in the guide to object cloning.
8. Private Constructors
A private constructor stops other classes from calling new. We use it for utility classes that hold only static methods, for static factory methods that cache or validate instances, and for the singleton pattern.
final class TipCalculator {
private TipCalculator() { } // no instances
static double tip(double bill, int percent) {
return bill * percent / 100;
}
}
double tip = TipCalculator.tip(80.0, 15); // 12.0
A private constructor also prevents subclassing from outside the class, because a subclass constructor would have to call it. Marking the class final as well makes the intent explicit.
9. Java Constructor FAQs
Constructor questions in interviews and search suggestions mostly test what a constructor is not allowed to do.
9.1. Can a constructor return a value?
No. A constructor has no return type, and new evaluates to the created object. A constructor body can use return; without a value to stop early, but not inside the Java 25 prologue.
9.2. Are constructors inherited in Java?
No. A subclass declares its own constructors and reaches the parent’s constructors only through super(…). That is why a subclass of a class with only a Reservation(String, int) constructor must declare a constructor too.
9.3. Can a constructor throw an exception?
Yes. Throwing from a constructor is the standard way to reject invalid arguments, and checked exceptions must be declared with throws. The caller never receives a reference to the half-built object.
9.4. Should a constructor call overridable methods?
Avoid it. The parent constructor runs before the subclass fields are assigned, so an override sees default values, as PartyNotice showed. Since Java 25, assigning those fields before super() fixes the problem when changing the parent is not an option.
9.5. Can we call super() after other statements?
Yes, since Java 25, as long as the statements before it do not use this. On Java 22 to 24 the feature was a preview that needed –enable-preview, and on older versions super(…) must be the first statement.
10. Conclusion
A constructor has the class name and no return type, and it runs once per new to put the object into a valid state. The compiler adds a default constructor only when we declare none, and frameworks such as JPA need a no-argument constructor that we sometimes add by hand.
Chaining with this() keeps the validation in one place, and super() runs the parent’s setup first. Java 25 lets us validate arguments and assign subclass fields before super(), and records add compact constructors for validation and normalization. Copy and private constructors cover copying objects and controlling how instances are created. Constructors give every object a valid starting state, which the other OOP concepts in Java build on.
11. References
- JLS 25, Constructor Declarations
- JLS 25, Record Constructor Declarations
- JEP 513, Flexible Constructor Bodies
- JLS 25, Creation of New Class Instances
- Oracle Java Tutorial, Providing Constructors for Your Classes
Happy Learning !!