Skip to content
Java oop basics 7 min read

Constructors

A constructor is a special method that runs automatically when you create an object with new. Its job is to initialize the object’s fields so every new object starts in a valid, predictable state — not full of garbage values.

What Makes a Constructor Special

A constructor looks like a method but follows three strict rules:

  • Its name must exactly match the class name (including case).
  • It has no return type — not even void.
  • It is called automatically by the JVM the moment new allocates memory for the object.
public class Car {
    String brand;
    int year;

    // constructor — same name as class, no return type
    Car(String brand, int year) {
        this.brand = brand;
        this.year  = year;
    }
}

Note: The this keyword used above refers to the current object. It lets you disambiguate between a field (this.brand) and a constructor parameter that has the same name (brand).

Default Constructor

If you do not write any constructor, the Java compiler silently inserts a default (no-argument) constructor that does nothing except call super(). The moment you write even one constructor yourself, the compiler stops generating the default one.

public class Point {
    int x;
    int y;
    // No constructor written — compiler adds:
    // Point() { super(); }
}

public class Main {
    public static void main(String[] args) {
        Point p = new Point(); // works fine
        System.out.println(p.x + ", " + p.y); // 0, 0  (int fields default to 0)
    }
}

Output:

0, 0

Warning: Once you add a parameterized constructor, new Point() will fail to compile unless you also explicitly write a no-arg constructor. This is a very common beginner mistake.

Parameterized Constructor

A parameterized constructor accepts arguments so you can supply initial values at the moment of creation — no need for separate setter calls.

public class Rectangle {
    double width;
    double height;

    Rectangle(double width, double height) {
        this.width  = width;
        this.height = height;
    }

    double area() {
        return width * height;
    }
}

public class Main {
    public static void main(String[] args) {
        Rectangle r = new Rectangle(5.0, 3.0);
        System.out.println("Area: " + r.area());
    }
}

Output:

Area: 15.0

Constructor Overloading

Just like method overloading, you can define multiple constructors in the same class as long as their parameter lists differ. The compiler picks the right one based on the arguments you pass.

public class Student {
    String name;
    int    age;

    // no-arg constructor — sensible defaults
    Student() {
        this.name = "Unknown";
        this.age  = 0;
    }

    // one-arg constructor
    Student(String name) {
        this.name = name;
        this.age  = 0;
    }

    // full constructor
    Student(String name, int age) {
        this.name = name;
        this.age  = age;
    }

    @Override
    public String toString() {
        return name + " (age " + age + ")";
    }
}

public class Main {
    public static void main(String[] args) {
        Student s1 = new Student();
        Student s2 = new Student("Alice");
        Student s3 = new Student("Bob", 22);

        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
    }
}

Output:

Unknown (age 0)
Alice (age 0)
Bob (age 22)

Constructor Chaining with this()

When one constructor calls another constructor in the same class, that is called constructor chaining. You use the special this(...) call for this. It must be the very first statement in the constructor body.

public class Circle {
    double radius;
    String color;

    Circle() {
        this(1.0, "black"); // delegates to the full constructor
    }

    Circle(double radius) {
        this(radius, "black");
    }

    Circle(double radius, String color) {
        this.radius = radius;
        this.color  = color;
    }

    @Override
    public String toString() {
        return color + " circle, r=" + radius;
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(new Circle());
        System.out.println(new Circle(5.0));
        System.out.println(new Circle(3.0, "red"));
    }
}

Output:

black circle, r=1.0
black circle, r=5.0
red circle, r=3.0

Constructor chaining keeps initialization logic in one place instead of duplicating it across multiple constructors — a clean, DRY approach.

Tip: this() and super() cannot both be the first statement. Java allows only one explicit constructor call at the top of a constructor body.

Calling the Parent Constructor with super()

When a class inherits from another, the child constructor must initialize the parent’s fields too. You do this with super(...). If you omit it, the compiler silently inserts super() (the no-arg parent constructor). If the parent has no no-arg constructor, the code won’t compile.

public class Animal {
    String name;

    Animal(String name) {
        this.name = name;
    }
}

public class Dog extends Animal {
    String breed;

    Dog(String name, String breed) {
        super(name);        // initialize Animal's field
        this.breed = breed;
    }

    @Override
    public String toString() {
        return name + " (" + breed + ")";
    }
}

public class Main {
    public static void main(String[] args) {
        Dog d = new Dog("Rex", "Labrador");
        System.out.println(d);
    }
}

Output:

Rex (Labrador)

See super Keyword for a full breakdown of super usage beyond constructors.

Copy Constructor

Java does not have a built-in copy constructor like C++, but the pattern is simple to implement manually: write a constructor that takes an object of the same type and copies its fields.

public class Point {
    int x;
    int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    // copy constructor
    Point(Point other) {
        this.x = other.x;
        this.y = other.y;
    }
}

public class Main {
    public static void main(String[] args) {
        Point original = new Point(3, 7);
        Point copy     = new Point(original);

        copy.x = 99; // modifying copy does NOT affect original
        System.out.println("original: " + original.x + ", " + original.y);
        System.out.println("copy    : " + copy.x     + ", " + copy.y);
    }
}

Output:

original: 3, 7
copy    : 99, 7

Warning: For fields that are reference types (arrays, other objects), a copy constructor doing this.field = other.field performs only a shallow copy — both objects point at the same inner object. For a deep copy you must recursively copy those references. See Object Cloning for the alternative clone() approach.

Private Constructors

Marking a constructor private prevents anyone outside the class from calling new. This is the backbone of the Singleton and Utility class patterns.

public class MathUtils {
    // Utility class — no instances allowed
    private MathUtils() { }

    public static int square(int n) {
        return n * n;
    }
}

// MathUtils m = new MathUtils(); // compile error!
System.out.println(MathUtils.square(5)); // 25

See static Keyword for more on why utility classes pair naturally with static methods.

Under the Hood

When you write new Car("Toyota", 2023), the JVM takes these steps:

  1. Memory allocation — The heap allocator reserves memory for a new Car object. All fields are zeroed out (numerics → 0, booleans → false, references → null).
  2. Constructor dispatch — The JVM pushes arguments onto the operand stack and invokes the matching constructor via the invokespecial bytecode instruction. invokespecial is used (rather than invokevirtual) because constructors are not polymorphic — they are always resolved at compile time.
  3. Parent initialization — Before anything else runs in your constructor, the JVM ensures super() or super(...) has been called, walking all the way up the chain to Object.
  4. Instance initializer blocks run after super() but before the rest of your constructor body (see Instance Initializer Block).
  5. Return of reference — The new expression evaluates to a reference to the fully initialized object, which is stored in your variable.

This sequencing guarantees that an object is never observable in a partially constructed state through normal code paths. The JVM spec calls this the creation and initialization protocol.

Note: Constructors are not inherited. A subclass must always define its own constructors (even if it just forwards to super). This is why Object — the root of all classes — provides its own no-arg constructor, so every class can implicitly chain up to it.

Quick Reference

FeatureSyntaxNotes
No-arg constructorClassName() { }Compiler auto-generates if none exists
ParameterizedClassName(Type p) { }Any number of parameters
OverloadedMultiple constructors, different paramsResolved at compile time
Chain to same classthis(args) — first lineAvoids duplicating init logic
Chain to parentsuper(args) — first lineRequired if parent has no no-arg
Copy constructorClassName(ClassName other)Must deep-copy reference fields manually
Private constructorprivate ClassName() { }Singleton, factory, utility classes
  • Classes & Objects — understand the blueprint before exploring how it gets initialized
  • this Keyword — how this disambiguates fields from parameters and chains constructors
  • super Keyword — calling parent constructors and methods in an inheritance hierarchy
  • Instance Initializer Block — another initialization mechanism that runs just before the constructor body
  • Object Cloning — an alternative to copy constructors for duplicating objects
  • Inheritance — why constructor chaining with super() matters so much in subclasses
Last updated June 13, 2026
Was this helpful?