Constructors

A constructor runs when an object is created and sets it up in a valid state. Instead of creating an empty object and filling in fields one by one (and maybe forgetting one), you pass everything the object needs to new.

Writing a constructor

JAVA
class Customer {
    String name;
    String email;

    Customer(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

public class Main {
    public static void main(String[] args) {
        Customer c = new Customer("Ana Pereira", "ana@example.com");
        System.out.println(c.name + " <" + c.email + ">");
    }
}
Output
Ana Pereira <ana@example.com>

A constructor has the same name as the class and no return type. new Customer(…) allocates the object and then runs the constructor with the arguments.

The default constructor

If a class declares no constructor, Java provides an empty one with no parameters — that's why new Product() worked in lesson 23. As soon as you write any constructor, that free one disappears:

JAVA
class Point {
    int x, y;

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

public class Main {
    public static void main(String[] args) {
        Point p = new Point();
    }
}
Compiler error
Main.java:12: error: constructor Point in class Point cannot be applied to given types;

Overloaded constructors and this(…)

JAVA
class Pizza {
    String size;
    int toppings;
    boolean extraCheese;

    Pizza(String size, int toppings, boolean extraCheese) {
        this.size = size;
        this.toppings = toppings;
        this.extraCheese = extraCheese;
    }

    Pizza(String size) {
        this(size, 0, false);
    }

    Pizza() {
        this("medium");
    }

    String describe() {
        return size + ", " + toppings + " toppings" + (extraCheese ? ", extra cheese" : "");
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(new Pizza("large", 3, true).describe());
        System.out.println(new Pizza("small").describe());
        System.out.println(new Pizza().describe());
    }
}
Output
large, 3 toppings, extra cheese
small, 0 toppings
medium, 0 toppings

A class can have several constructors with different parameters. this(…) calls another constructor of the same class, so the real work happens in one place. It must be the first statement.

Validating in the constructor

JAVA
class Percentage {
    final int value;

    Percentage(int value) {
        if (value < 0 || value > 100) {
            throw new IllegalArgumentException("Percentage must be 0-100, got " + value);
        }
        this.value = value;
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(new Percentage(40).value);
        new Percentage(140);
    }
}
Output
40
Exception in thread "main" java.lang.IllegalArgumentException: Percentage must be 0-100, got 140
    at Percentage.<init>(Main.java:6)
    at Main.main(Main.java:15)

Because every object passes through a constructor, it's the ideal place to reject invalid data. Combined with final fields, an object can then never be in an invalid state. In stack traces, constructors appear as <init>.

final fields

JAVA
class Order {
    final int id;
    final String customer;

    Order(int id, String customer) {
        this.id = id;
        this.customer = customer;
    }

    void rename(String newCustomer) {
        this.customer = newCustomer;
    }
}

public class Main {
    public static void main(String[] args) {
    }
}
Compiler error
Main.java:11: error: cannot assign a value to final variable customer

A final field must be set exactly once, in the constructor (or its declaration), and can't change afterwards. Objects whose fields are all final are immutable: simpler to reason about and safe to share. Records (lesson 32) make such classes very short.

Initialisation order

JAVA
class Demo {
    int a = 1;
    int b;

    {
        b = a + 1;
        System.out.println("initialiser block: a=" + a + ", b=" + b);
    }

    Demo() {
        System.out.println("constructor: a=" + a + ", b=" + b);
    }
}

public class Main {
    public static void main(String[] args) {
        new Demo();
    }
}
Output
initialiser block: a=1, b=2
constructor: a=1, b=2

Field initialisers and initialiser blocks run in order, before the constructor body. In practice, keep setup in constructors so it's in one place.

Try it yourself

Write a BankAccount with an owner, an optional starting balance, and validation that the balance isn't negative.

Try it yourself · Main.java
Edit the code, copy it into Main.java and run java Main.java (Java 25+). How to set up Java
Output
Mei: 250.0
Lucas: 40.0