Constructor Overload

Su Suriya Ravichandran Updated 14 Nov 2026
8 min read ·Lesson 46 of 133

Constructor overloading means giving a class more than one constructor, each with a different parameter list. It lets callers create objects in whichever way suits the information they have, while the class still guarantees every object starts in a valid state. String, ArrayList, Thread, and most JDK classes offer several constructors for exactly this reason.

Basic Overloading

public class Rectangle {
    private final double width;
    private final double height;

    public Rectangle() {                         // no arguments: a unit square
        width = 1;
        height = 1;
    }

    public Rectangle(double side) {              // one argument: a square
        width = side;
        height = side;
    }

    public Rectangle(double width, double height) {   // two arguments: general case
        this.width = width;
        this.height = height;
    }

    public double area() { return width * height; }
}
System.out.println(new Rectangle().area());        // 1.0
System.out.println(new Rectangle(5).area());       // 25.0
System.out.println(new Rectangle(4, 3).area());    // 12.0

The compiler decides which constructor to run by examining the arguments in each new expression. All three constructors produce a Rectangle; they differ only in how much the caller must specify.

How the Compiler Chooses

Constructors are distinguished by their parameter lists: the number, types, and order of parameters. Parameter names do not count.

public Rectangle(double width, double height) { ... }
public Rectangle(double height, double width) { ... }   // compile error: same signature (double, double)

Both constructors take (double, double). Different names make no difference to the compiler, so the class would have two constructors with the same signature, which is not allowed.

Valid overloads differ in at least one of these:

DifferenceExample
Number of parametersBox(), Box(int), Box(int, int)
Parameter typesTemperature(double celsius), Temperature(String text)
Order of different typesItem(String, int), Item(int, String)

The last form is legal but invites mistakes, since swapping two arguments silently selects a different constructor. Avoid overloads that differ only in parameter order.

Matching rules

When arguments do not match any constructor exactly, the compiler applies conversions in a fixed order of preference:

  1. An exact match, or a widening primitive conversion (int → long → double).
  2. Autoboxing or unboxing (int ↔ Integer).
  3. Varargs.
public class Demo {
    Demo(long x)    { System.out.println("long"); }
    Demo(Integer x) { System.out.println("Integer"); }
    Demo(int... x)  { System.out.println("varargs"); }
}

new Demo(5);    // prints "long": widening is preferred over boxing and varargs

If two constructors are equally good matches, the call is ambiguous and does not compile:

public class Pair {
    Pair(Integer a, long b) {}
    Pair(long a, Integer b) {}
}

new Pair(1, 2);   // compile error: reference to Pair is ambiguous

Constructor Chaining with this(...)

Writing the same assignments and validation in every constructor duplicates code and invites inconsistencies. Instead, one constructor can call another in the same class with this(...):

public class Rectangle {
    private final double width;
    private final double height;

    public Rectangle(double width, double height) {
        if (width <= 0 || height <= 0) {
            throw new IllegalArgumentException("dimensions must be positive");
        }
        this.width = width;
        this.height = height;
    }

    public Rectangle(double side) {
        this(side, side);          // reuse the two-argument constructor
    }

    public Rectangle() {
        this(1);                   // reuse the one-argument constructor
    }
}
new Rectangle()  →  this(1)  →  this(1, 1)  →  validation and assignment

Validation now exists in exactly one place. Every path to creating a Rectangle passes through it, so new Rectangle(-5) is rejected the same way as new Rectangle(-5, 2).

The principal-constructor pattern

The most complete constructor, the one that takes every value, does the real work. The others are shortcuts that fill in defaults and delegate to it. This keeps behavior consistent and makes it obvious where to look when a class's initialization needs to change.

Rules for this(...)

  • this(...) can call only constructors of the same class; super(...) calls the parent's.
  • A constructor can contain at most one this(...) or super(...) call, not both.
  • On Java 24 and earlier, the call must be the first statement in the constructor. Java 25 allows statements before it, provided they do not use the object under construction.
  • Chains must end. A cycle is a compile error:
public Loop()      { this(0); }
public Loop(int x) { this(); }   // compile error: recursive constructor invocation

Overloading with Different Input Types

Overloads are especially useful when the same object can be built from different representations:

import java.time.LocalTime;

public class Duration24 {
    private final int totalMinutes;

    public Duration24(int minutes) {
        if (minutes < 0) throw new IllegalArgumentException("negative duration");
        this.totalMinutes = minutes;
    }

    public Duration24(int hours, int minutes) {
        this(hours * 60 + minutes);
    }

    public Duration24(String text) {               // "2h 30m", "45m", "1h"
        this(parse(text));
    }

    public Duration24(LocalTime start, LocalTime end) {
        this((int) java.time.Duration.between(start, end).toMinutes());
    }

    private static int parse(String text) {
        int minutes = 0;
        for (String part : text.strip().split("\\s+")) {
            int value = Integer.parseInt(part.substring(0, part.length() - 1));
            char unit = part.charAt(part.length() - 1);
            if (unit == 'h') minutes += value * 60;
            else if (unit == 'm') minutes += value;
            else throw new IllegalArgumentException("Unknown unit in: " + part);
        }
        return minutes;
    }

    @Override
    public String toString() {
        return totalMinutes / 60 + "h " + totalMinutes % 60 + "m";
    }
}
System.out.println(new Duration24(150));                                  // 2h 30m
System.out.println(new Duration24(1, 45));                                // 1h 45m
System.out.println(new Duration24("3h 15m"));                             // 3h 15m
System.out.println(new Duration24(LocalTime.of(9, 0), LocalTime.of(17, 30)));  // 8h 30m

Every constructor converts its input into minutes and delegates to Duration24(int), which performs the only validation. The String constructor calls a private static helper inside the this(...) call. The helper must be static because it runs before the object is initialized, when instance methods cannot yet be used.

Overloaded Constructors in the JDK

ClassConstructorsPurpose
ArrayList(), (int initialCapacity), (Collection c)Empty, pre-sized, or copied from another collection
String(), (char[]), (byte[], Charset), (StringBuilder), ...Build text from many sources
Thread(), (Runnable), (Runnable, String name), ...Different levels of configuration
Scanner(InputStream), (File), (Path), (String)Read from different input sources
List<String> a = new ArrayList<>();               // default capacity
List<String> b = new ArrayList<>(10_000);         // avoid repeated resizing for a large list
List<String> c = new ArrayList<>(List.of("x", "y"));   // copy of another collection

When Overloading Becomes a Problem

As optional parameters multiply, constructor overloads multiply too, and calls become hard to read:

new Pizza("large", true, false, true, false);
// which boolean is cheese? which is olives?

Constructors with many parameters of the same type are error-prone because the compiler cannot catch swapped arguments. Two alternatives scale better.

Static factory methods with descriptive names

public class Rectangle {
    private final double width, height;

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

    public static Rectangle of(double width, double height) { return new Rectangle(width, height); }
    public static Rectangle square(double side)             { return new Rectangle(side, side); }
    public static Rectangle unit()                          { return new Rectangle(1, 1); }
}

Names state intent (square versus of), and two factory methods with the same parameter types can coexist, which two constructors cannot.

The Builder pattern

For objects with many optional settings, a builder names each value as it is set:

public class Pizza {
    private final String size;
    private final boolean cheese, olives, mushrooms;

    private Pizza(Builder b) {
        this.size = b.size;
        this.cheese = b.cheese;
        this.olives = b.olives;
        this.mushrooms = b.mushrooms;
    }

    public static class Builder {
        private final String size;              // required
        private boolean cheese, olives, mushrooms;

        public Builder(String size) { this.size = size; }
        public Builder cheese()     { cheese = true; return this; }
        public Builder olives()     { olives = true; return this; }
        public Builder mushrooms()  { mushrooms = true; return this; }
        public Pizza build()        { return new Pizza(this); }
    }

    @Override
    public String toString() {
        return size + (cheese ? " +cheese" : "") + (olives ? " +olives" : "")
                + (mushrooms ? " +mushrooms" : "");
    }
}
Pizza p = new Pizza.Builder("large").cheese().mushrooms().build();
System.out.println(p);    // large +cheese +mushrooms

Required values go into the builder's constructor; optional ones are set by name, in any order. HttpRequest.newBuilder(), StringBuilder, and many libraries' configuration APIs follow this pattern.

SituationApproach
Two to four parameters, few combinationsOverloaded constructors with this(...) chaining
Variants that need meaningful namesStatic factory methods
Many optional parametersBuilder

Practical Example: Notification Settings

public class Notification {
    private final String recipient;
    private final String message;
    private final String channel;
    private final int priority;

    public Notification(String recipient, String message, String channel, int priority) {
        if (recipient == null || recipient.isBlank()) {
            throw new IllegalArgumentException("recipient required");
        }
        if (!channel.equals("email") && !channel.equals("sms") && !channel.equals("push")) {
            throw new IllegalArgumentException("unknown channel: " + channel);
        }
        if (priority < 1 || priority > 5) {
            throw new IllegalArgumentException("priority must be 1-5");
        }
        this.recipient = recipient;
        this.message = message;
        this.channel = channel;
        this.priority = priority;
    }

    public Notification(String recipient, String message, String channel) {
        this(recipient, message, channel, 3);
    }

    public Notification(String recipient, String message) {
        this(recipient, message, recipient.contains("@") ? "email" : "sms");
    }

    @Override
    public String toString() {
        return "[" + channel + " p" + priority + "] " + recipient + ": " + message;
    }

    public static void main(String[] args) {
        System.out.println(new Notification("ada@example.com", "Class starts at 10"));
        System.out.println(new Notification("+919000000000", "OTP is 4821"));
        System.out.println(new Notification("ada@example.com", "Server down", "push", 5));
    }
}

Output:

[email p3] ada@example.com: Class starts at 10
[sms p3] +919000000000: OTP is 4821
[push p5] ada@example.com: Server down

The four-argument constructor is the principal one and holds all validation. The three-argument form supplies a default priority. The two-argument form picks a channel automatically, using an expression inside this(...). One caveat: because that expression calls recipient.contains(...) before the principal constructor's null check, passing null here throws NullPointerException instead of the friendlier IllegalArgumentException. Moving the decision into a static helper that handles null first would fix it. Callers choose how much to specify, and every notification, however it was created, passes the same checks.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Constructor Overload and get answers drawn from it.

Signed-in readers only.