Skip to main content

6.4 Method Overloading

One of the powerful features Java supports is declaring multiple methods with the same name within a class. As long as the parameters are configured differently, methods with the same name are recognized as separate methods.

1. What Is Overloading?

Normally, an identifier (name) must be unique within a class. However, as long as the parameter types, count, or order are different, multiple methods can be declared with the same name. This is called Method Overloading.

2. Conditions for Overloading

ConditionDetails
Method nameMust be the same
Parameter typesDifferent types satisfy overloading
Parameter countDifferent counts satisfy overloading
Parameter orderDifferent order (with different types) also satisfies overloading
Return typeIrrelevant(return type alone cannot distinguish overloads)
class OverloadingTest {
// 1. Different number of parameters -> overloading applies
int add(int a, int b) { return a + b; }
int add(int a, int b, int c) { return a + b + c; }

// 2. Different parameter types -> overloading applies
double add(double a, double b) { return a + b; }
long add(long a, long b) { return a + b; }

// 3. Different parameter order -> overloading applies (when types differ)
void print(String s, int n) { System.out.println(s + ": " + n); }
void print(int n, String s) { System.out.println(n + " - " + s); }

// Non-overloading examples (compile errors):
// int add(int a, int b) { ... } // already exists -> duplicate error
// double add(int a, int b) { ... } // only return type differs -> not overloading
}
Only Return Type Differs

A method that differs only in return type is not an overload. The compiler determines which method to call based only on the method name and parameters, so it cannot differentiate by return type alone.

3. System.out.println() Is the Classic Example of Overloading

The reason System.out.println() accepts any type of value is that the println method is overloaded for dozens of types.

// Inside Java standard library PrintStream class (conceptual example)
public class PrintStream {
public void println(boolean x) { ... }
public void println(char x) { ... }
public void println(int x) { ... }
public void println(long x) { ... }
public void println(float x) { ... }
public void println(double x) { ... }
public void println(String x) { ... }
public void println(Object x) { ... }
// ... 10+ overloads
}

// Thanks to this, we don't need to memorize different names for each type
System.out.println(42); // calls the int version
System.out.println(3.14); // calls the double version
System.out.println("Java"); // calls the String version
System.out.println(true); // calls the boolean version

4. Overloading vs Overriding

The two concepts have similar names but are completely different.

AspectOverloadingOverriding
Where it occursInside the same classIn a child class (inheritance)
Method nameSameSame
ParametersDifferentMust be exactly the same
Return typeCan differMust be the same (covariant return allowed)
Determined atCompile time(static binding)Runtime(dynamic binding)
KeywordNone@Override annotation recommended
class Animal {
// Overloading: same name, different parameters within the same class
void sound() { System.out.println("..."); }
void sound(int times) { for(int i=0;i<times;i++) sound(); }
void sound(String prefix) { System.out.println(prefix + "..."); }
}

class Dog extends Animal {
// Overriding: redefines a parent method (same parameters)
@Override
void sound() { System.out.println("Woof!"); }
}

5. Auto Type Promotion and Overloading

When no overloaded method exactly matches the argument type, Java applies automatic type promotion to select the closest matching method.

public class AutoCastOverload {

static void print(int n) { System.out.println("int: " + n); }
static void print(long n) { System.out.println("long: " + n); }
static void print(double n) { System.out.println("double: " + n); }

public static void main(String[] args) {
print(10); // int: 10 (exact match: int -> int)
print(10L); // long: 10 (exact match: long -> long)
print(10.0); // double: 10.0 (exact match: double -> double)

byte b = 5;
print(b); // int: 5 (byte -> int auto promotion; int is the closest)

float f = 3.14f;
print(f); // double: 3.14 (float -> double auto promotion)
}
}
Type Promotion Priority

Auto promotion (widening) follows: byte -> short -> int -> long -> float -> double. When overloading, the method with the next wider type is selected.

6. Varargs and Overloading

Introduced in Java 5, variable arguments (varargs) allow you to write a method that accepts a variable number of parameters.

public class VarargsExample {

// int... is internally identical to int[]
static int sum(int... numbers) {
int total = 0;
for (int n : numbers) {
total += n;
}
return total;
}

// Mixing varargs with fixed parameters
static void log(String prefix, Object... messages) {
System.out.print("[" + prefix + "] ");
for (Object msg : messages) {
System.out.print(msg + " ");
}
System.out.println();
}

public static void main(String[] args) {
System.out.println(sum()); // 0 (no arguments)
System.out.println(sum(1, 2, 3)); // 6
System.out.println(sum(1, 2, 3, 4, 5)); // 15

log("INFO", "Server started");
log("ERROR", "Connection failed", "Code:", 404);
log("DEBUG", "a=", 10, "b=", 20, "sum=", 30);
}
}
Varargs Caution
  • Varargs can only be declared once per method and only as the last parameter.
  • Overloading a varargs method with an array parameter method can cause conflicts.

7. Advantage of Overloading: API Convenience

Overloading shines especially in library and API design. It allows users to easily use the same functionality with a variety of input forms.

8. Practical Example: Logger Class

Implementing a useful Logger class with overloading.

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class Logger {

private static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

private String prefix;
private boolean enabled;

public Logger(String prefix) {
this.prefix = prefix;
this.enabled = true;
}

// Overload 1: message only
public void log(String message) {
print("INFO", message);
}

// Overload 2: level + message
public void log(String level, String message) {
print(level, message);
}

// Overload 3: message + integer data
public void log(String message, int value) {
print("INFO", message + " = " + value);
}

// Overload 4: message + double data
public void log(String message, double value) {
print("INFO", String.format("%s = %.2f", message, value));
}

// Overload 5: exception logging
public void log(String message, Exception e) {
print("ERROR", message + " [" + e.getClass().getSimpleName() + ": " + e.getMessage() + "]");
}

// Overload 6: format string
public void log(String format, Object... args) {
print("INFO", String.format(format, args));
}

private void print(String level, String message) {
if (!enabled) return;
String time = LocalDateTime.now().format(FORMATTER);
System.out.printf("[%s][%s][%s] %s%n", time, level, prefix, message);
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

public class LoggerTest {
public static void main(String[] args) {
Logger logger = new Logger("MyApp");

// Various overloaded log() calls
logger.log("Server has started");
logger.log("WARN", "Memory usage is high");
logger.log("Current connections", 42);
logger.log("CPU usage", 78.5);
logger.log("Users: %d, Server: %s", 1500, "prod-01");

try {
int result = 10 / 0;
} catch (ArithmeticException e) {
logger.log("Calculation error occurred", e);
}
}
}

Sample Output:

[2025-01-01 12:00:00][INFO][MyApp] Server has started
[2025-01-01 12:00:00][WARN][MyApp] Memory usage is high
[2025-01-01 12:00:00][INFO][MyApp] Current connections = 42
[2025-01-01 12:00:00][INFO][MyApp] CPU usage = 78.50
[2025-01-01 12:00:00][INFO][MyApp] Users: 1500, Server: prod-01
[2025-01-01 12:00:00][ERROR][MyApp] Calculation error occurred [ArithmeticException: / by zero]

9. Constructor Overloading Usage

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

// Square: one side length
public Rectangle(double side) {
this(side, side);
}

// Rectangle: width x height
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}

// 0x0 default rectangle
public Rectangle() {
this(0, 0);
}

public double area() { return width * height; }
public double perimeter() { return 2 * (width + height); }

@Override
public String toString() {
return String.format("Rectangle(%.1f x %.1f)", width, height);
}
}

public class RectangleTest {
public static void main(String[] args) {
Rectangle square = new Rectangle(5); // square
Rectangle rect = new Rectangle(4, 7); // rectangle
Rectangle empty = new Rectangle(); // default

System.out.println(square + " area: " + square.area()); // 25.0
System.out.println(rect + " area: " + rect.area()); // 28.0
System.out.println(empty + " area: " + empty.area()); // 0.0
}
}

Summary

ConceptKey Content
OverloadingSame name, different parameters (type/count/order)
Overloading conditionParameters must differ (return type is irrelevant)
Classic exampleSystem.out.println()
Overloading vs OverridingCompile-time vs runtime decision
Auto type promotionIf no exact type match, the next wider type's method is chosen
Varargstype... form, only as the last parameter
AdvantageAPI convenience, intuitive method naming