Method Overload

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

Method overloading means declaring several methods with the same name but different parameter lists in the same class (or across a class and its subclasses). Callers use one meaningful name, and the compiler picks the right version from the arguments. System.out.println is the most familiar example: one name, with versions for int, double, char, boolean, String, Object, and more.

Basic Example

public class AreaCalculator {
    static double area(double radius) {                 // circle
        return Math.PI * radius * radius;
    }

    static double area(double width, double height) {   // rectangle
        return width * height;
    }

    static double area(double a, double b, double c) {  // triangle, by its three sides
        double s = (a + b + c) / 2;
        return Math.sqrt(s * (s - a) * (s - b) * (s - c));
    }

    public static void main(String[] args) {
        System.out.printf("%.2f%n", area(2));          // 12.57
        System.out.printf("%.2f%n", area(4, 5));       // 20.00
        System.out.printf("%.2f%n", area(3, 4, 5));    // 6.00
    }
}

All three methods answer the same question, "what is the area?", for different inputs. Without overloading they would need names like circleArea, rectangleArea, and triangleArea. Overloading is appropriate exactly when the versions perform the same conceptual operation.

What Makes Overloads Different

Two methods are overloads if they have the same name and different signatures. A method's signature is its name plus its parameter types, in order:

DifferenceValid overloads
Number of parametersmax(int, int) and max(int, int, int)
Parameter typesparse(String) and parse(char[])
Order of differing typeslog(String, int) and log(int, String)

What does not count:

int total(int a, int b) { ... }
double total(int a, int b) { ... }       // compile error: return type alone does not distinguish

void save(String name) { ... }
void save(String title) { ... }          // compile error: parameter names do not matter

void run(int x) throws IOException { ... }
void run(int x) { ... }                  // compile error: throws clause does not matter

The return type cannot distinguish overloads because a call such as total(1, 2); whose result is discarded gives the compiler no way to choose.

How the Compiler Chooses an Overload

The choice happens at compile time, using the declared types of the arguments. When there is no exact match, the compiler considers conversions in three phases, stopping at the first phase that finds a match:

  1. Exact match or widening primitive conversion (int → long → float → double), and subtype-to-supertype for references.
  2. Boxing or unboxing (int ↔ Integer).
  3. Varargs.

Within a phase, if several methods match, it picks the most specific one: the method whose parameter types are the narrowest.

static void show(long x)     { System.out.println("long"); }
static void show(Integer x)  { System.out.println("Integer"); }
static void show(Object x)   { System.out.println("Object"); }
static void show(int... x)   { System.out.println("varargs"); }

show(5);                 // long     (phase 1: widening beats boxing)
show(Integer.valueOf(5));// Integer  (phase 1: exact reference match)
show("text");            // Object   (phase 1: String is a subtype of Object)
show();                  // varargs  (only varargs accepts zero arguments)

The first result surprises many people: an int argument chose long over Integer. Widening was part of Java before autoboxing existed, and the phases preserve the old behavior so existing code did not change meaning.

Most specific wins

static void handle(Object o)  { System.out.println("Object"); }
static void handle(String s)  { System.out.println("String"); }

handle("hello");         // String: both match, String is more specific
handle(null);            // String: null fits both; String is more specific

Ambiguity

If no single method is most specific, the call does not compile:

static void pick(Integer a, long b) { }
static void pick(long a, Integer b) { }

pick(1, 2);              // compile error: reference to pick is ambiguous
static void find(String s)   { }
static void find(Integer i)  { }

find(null);              // compile error: ambiguous; neither is more specific
find((String) null);     // fine: the cast selects the version

Overloading Is Resolved by Declared Type

Because the choice is made at compile time, the declared type of an argument matters, not its runtime type:

static void describe(Object o) { System.out.println("an object"); }
static void describe(String s) { System.out.println("a string"); }

Object value = "hello";
describe(value);         // "an object": value is declared as Object
describe((String) value);// "a string"

This is the key difference from method overriding, where the runtime type of the object selects the method. Overloading never looks at the runtime type of the arguments.

Common Uses

Convenience versions with defaults

Java has no default parameter values. Overloads that delegate to a fuller version provide the same effect:

public class Logger {
    public void log(String message) {
        log(message, "INFO");
    }

    public void log(String message, String level) {
        log(message, level, System.currentTimeMillis());
    }

    public void log(String message, String level, long timestamp) {
        System.out.printf("%d [%s] %s%n", timestamp, level, message);
    }
}

All the real work happens in one method. The shorter versions only supply defaults, so behavior stays consistent and changes are made in one place.

Accepting different input types

public class Temperature {
    public static double toFahrenheit(double celsius) {
        return celsius * 9 / 5 + 32;
    }

    public static double toFahrenheit(String celsiusText) {
        return toFahrenheit(Double.parseDouble(celsiusText.strip()));
    }

    public static double[] toFahrenheit(double... celsiusValues) {
        double[] out = new double[celsiusValues.length];
        for (int i = 0; i < out.length; i++) {
            out[i] = toFahrenheit(celsiusValues[i]);
        }
        return out;
    }
}
Temperature.toFahrenheit(100);           // 212.0 (double version, int widened)
Temperature.toFahrenheit(" 37 ");        // about 98.6 (String version)
Temperature.toFahrenheit(0, 25, 40);     // [32.0, 77.0, 104.0] (varargs version)

The single-value call toFahrenheit(100) selects the double version in phase 1, before varargs is ever considered, so the two overloads do not conflict.

Overloads in the JDK

MethodOverloads for
System.out.printlnevery primitive type, char[], String, Object
Math.max, Math.absint, long, float, double
String.valueOfevery primitive, char[], Object
Arrays.sortevery primitive array, Object[], ranges, comparators
List.of0 to 10 elements, plus varargs

List.of has fixed-arity overloads for up to ten elements, plus a varargs version, because calling a fixed-arity method avoids creating a varargs array on every call.

Overloading Across Inheritance

A subclass can add overloads of an inherited method:

class Printer {
    void print(String s) { System.out.println("text: " + s); }
}

class ColorPrinter extends Printer {
    void print(String s, String color) {           // overload, not override
        System.out.println(color + " text: " + s);
    }
}

ColorPrinter p = new ColorPrinter();
p.print("hi");             // text: hi    (inherited)
p.print("hi", "red");      // red text: hi

Be careful not to confuse an accidental overload with an intended override:

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

    public boolean equals(Point other) {          // OVERLOAD of equals(Object), not an override!
        return x == other.x && y == other.y;
    }
}

Object a = new Point(1, 2);
Point b = new Point(1, 2);
System.out.println(b.equals(new Point(1, 2)));   // true: calls equals(Point)
System.out.println(a.equals(b));                 // false: calls Object.equals(Object)
System.out.println(List.of(b).contains(new Point(1, 2)));   // false: collections call equals(Object)

equals(Point) does not override Object.equals(Object), because the parameter type differs. Collections call equals(Object), so the custom version is ignored. Writing @Override on the method would have produced a compile error immediately, revealing the mistake. This is a strong reason to use @Override on every method meant to override.

Guidelines

  • Overload only for the same operation. If two methods do different things, give them different names.
  • Avoid overloads with the same number of parameters of closely related types. remove(int index) and remove(Object element) on List are a well-known source of bugs: list.remove(1) on a List<Integer> removes the element at index 1, not the value 1.
  • Delegate rather than duplicate. Shorter overloads should call the complete one.
  • Be cautious mixing overloads with varargs and boxing. The resolution rules are precise but rarely intuitive; prefer distinct names when results could surprise a reader.
List<Integer> nums = new ArrayList<>(List.of(10, 20, 30));
nums.remove(1);                          // removes index 1 → [10, 30]
nums.remove(Integer.valueOf(10));        // removes the value 10 → [30]

Practical Example: Report Formatter

import java.time.LocalDate;
import java.util.List;

public class Format {
    static String cell(String text, int width) {
        if (text.length() > width) {
            return text.substring(0, width - 1) + "…";
        }
        return String.format("%-" + width + "s", text);
    }

    static String cell(double amount, int width) {
        return String.format("%," + width + ".2f", amount);
    }

    static String cell(int count, int width) {
        return String.format("%" + width + "d", count);
    }

    static String cell(LocalDate date, int width) {
        return cell(date.toString(), width);
    }

    static String row(Object... values) {
        StringBuilder sb = new StringBuilder();
        for (Object v : values) {
            sb.append(v).append(" | ");
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        record Sale(String product, int qty, double amount, LocalDate date) {}

        List<Sale> sales = List.of(
            new Sale("Wireless Keyboard", 3, 7497, LocalDate.of(2026, 11, 2)),
            new Sale("USB-C Docking Station Pro", 1, 12999.5, LocalDate.of(2026, 11, 5))
        );

        for (Sale s : sales) {
            System.out.println(row(
                cell(s.product(), 18),
                cell(s.qty(), 4),
                cell(s.amount(), 12),
                cell(s.date(), 10)
            ));
        }
    }
}

Output:

Wireless Keyboard  |    3 |     7,497.00 | 2026-11-02 | 
USB-C Docking Sta… |    1 |    12,999.50 | 2026-11-05 | 

One name, cell, formats any kind of value to a fixed width, and the compiler picks the version from the argument's type: text is left-aligned and truncated, whole numbers are right-aligned, money gets two decimals and thousands separators, and dates delegate to the String version. s.qty() is an int, so it selects cell(int, int) exactly rather than widening to double. In the format strings, flags such as - and , must come before the width (%,12.2f, not %12,.2f), which is why the width is inserted after them. Each overload performs the same conceptual job, which is exactly the situation overloading is designed for.

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 Method Overload and get answers drawn from it.

Signed-in readers only.