Java Method Overloading β Syntax, Rules, Examples & Best Practices
Everything you need to know about Java method overloading β rules, type promotion, return type constraints, varargs overloading, constructor overloading, overloading vs overriding, compile-time polymorphism, anti-patterns, and real-world production code examples.
Last Updated
March 2026
Read Time
22 min
Level
Beginner
Chapter
18 of 35
What is Method Overloading in Java?
Method overloading in Java is the ability to define multiple methods with the same name in the same class, as long as their parameter lists are different. The Java compiler selects the correct version of the method at compile time based on the number, types, and order of the arguments passed β making it a form of compile-time polymorphism (also called static polymorphism or ad hoc polymorphism).
Without overloading, you would need different method names for logically identical operations on different data types β addInt(int, int), addDouble(double, double), addLong(long, long). Overloading lets all three simply be called add(), making the API cleaner and more intuitive. In real applications, overloading is everywhere: System.out.println() has 10 overloaded versions, String.valueOf() has 9, and Math.max() has 4.
Overloading is resolved entirely at compile time β the JVM does not make any runtime decision about which overload to invoke. This makes overloaded method calls as efficient as direct method calls. It is completely different from method overriding, which involves runtime dispatch and inheritance.
Rules of Method Overloading β What the Compiler Checks
The Java compiler enforces a precise set of rules to determine whether two methods constitute valid overloads or a duplicate definition error. Understanding these rules prevents compile errors and unexpected resolution behavior.
Overloaded methods MUST share the exact same name (case-sensitive). 'print' and 'Print' are different methods, not overloads. The same name is the entire point of overloading β providing multiple implementations under one intuitive name.
The parameter list must differ in at least one of: (a) Number of parameters β add(int) vs add(int, int). (b) Type of parameters β add(int, int) vs add(double, double). (c) Order of parameter types β display(String, int) vs display(int, String). Parameter NAMES do not matter β display(int a, String b) and display(int x, String y) are the same signature.
Two methods with the same name and same parameter list but different return types cause a COMPILE ERROR β they are not valid overloads. Java cannot distinguish them from the call site alone: 'add(1,2)' gives no information about which return type is expected. This is a fundamental rule: return type never participates in overload resolution.
Overloaded methods can have different access modifiers (public, private, protected, package-private) and different throws clauses. These do not affect overload resolution β the compiler selects the overload purely from the parameter list, then applies the access check.
Overloaded methods can be defined in the same class. A subclass can also add overloads to an inherited method by defining a method with the same name but a different parameter list. This is still overloading (not overriding), resolved at compile time.
Static methods follow the same overloading rules as instance methods. Math.min(int,int), Math.min(long,long), Math.min(float,float), Math.min(double,double) are all static overloads. Overloading is independent of whether methods are static or instance.
public class OverloadingRules {
// β
VALID: Different number of parameters
public int add(int a) { return a; }
public int add(int a, int b) { return a + b; }
public int add(int a, int b, int c) { return a + b + c; }
// β
VALID: Different parameter types
public double multiply(int a, int b) { return (double)(a * b); }
public double multiply(double a, double b) { return a * b; }
public double multiply(long a, long b) { return (double)(a * b); }
// β
VALID: Different order of parameter types
public void display(String name, int age) {
System.out.println(name + " is " + age);
}
public void display(int age, String name) {
System.out.println(age + " years old: " + name);
}
// β INVALID: Only return type differs β COMPILE ERROR
// public int getValue() { return 1; }
// public double getValue() { return 1.0; } // ERROR: duplicate method
// β INVALID: Only parameter names differ β COMPILE ERROR
// public void show(int x) { }
// public void show(int y) { } // ERROR: duplicate method (same signature)
// β
VALID: Different access modifiers (both compile)
public void log(String msg) { System.out.println("[PUB] " + msg); }
private void log(String msg, int id) { System.out.println("[PRI] " + id + ": " + msg); }
public static void main(String[] args) {
OverloadingRules obj = new OverloadingRules();
System.out.println(obj.add(5)); // 5
System.out.println(obj.add(5, 3)); // 8
System.out.println(obj.add(5, 3, 2)); // 10
obj.display("Kavya", 22); // Kavya is 22
obj.display(22, "Kavya"); // 22 years old: Kavya
}
}Overloading by Number of Parameters
The simplest and most readable form of overloading: defining the same method for different numbers of arguments. This is the Java idiom for default parameter values β since Java has no built-in default arguments (unlike Python or Kotlin), overloading is how you provide optional parameters cleanly.
public class OverloadByCount {
// Overloading to simulate default parameters
// 1 param β uses defaults for missing values
public static void createConnection(String host) {
createConnection(host, 3306); // default port
}
// 2 params β uses default for timeout
public static void createConnection(String host, int port) {
createConnection(host, port, 30); // default timeout
}
// 3 params β full implementation (all others delegate here)
public static void createConnection(String host, int port, int timeoutSeconds) {
System.out.println("Connecting to " + host + ":" + port
+ " (timeout=" + timeoutSeconds + "s)");
}
// Another example: area calculation
// Square β 1 side
public static double area(double side) {
return side * side;
}
// Rectangle β 2 sides
public static double area(double length, double width) {
return length * width;
}
// Cuboid surface area β 3 dimensions
public static double area(double length, double width, double height) {
return 2 * (length * width + width * height + height * length);
}
public static void main(String[] args) {
// Connection with progressive defaults
createConnection("db.example.com");
// Output: Connecting to db.example.com:3306 (timeout=30s)
createConnection("db.example.com", 5432);
// Output: Connecting to db.example.com:5432 (timeout=30s)
createConnection("db.example.com", 5432, 60);
// Output: Connecting to db.example.com:5432 (timeout=60s)
// Area calculations
System.out.println(area(5.0)); // 25.0 (square)
System.out.println(area(5.0, 3.0)); // 15.0 (rectangle)
System.out.println(area(5.0, 3.0, 2.0)); // 62.0 (cuboid surface)
}
}Overloading by Type of Parameters
Overloading by parameter type is the most common form β providing type-specific implementations under a single method name. This is how the Java standard library achieves a clean API: Math.abs(int), Math.abs(long), Math.abs(float), Math.abs(double) all share one name and the compiler picks the right version automatically.
public class OverloadByType {
// Overloading by numeric type β mirrors Math.max() pattern
public static int max(int a, int b) { return (a >= b) ? a : b; }
public static long max(long a, long b) { return (a >= b) ? a : b; }
public static float max(float a, float b) { return (a >= b) ? a : b; }
public static double max(double a, double b) { return (a >= b) ? a : b; }
// Overloading by object type β print different info per type
public static void describe(int value) {
System.out.println("Integer: " + value + " (binary: " + Integer.toBinaryString(value) + ")");
}
public static void describe(double value) {
System.out.println("Double: " + value + " (formatted: " + String.format("%.2f", value) + ")");
}
public static void describe(String value) {
System.out.println("String: \"" + value + "\" (length=" + value.length() + ")");
}
public static void describe(boolean value) {
System.out.println("Boolean: " + value + " (" + (value ? "TRUE" : "FALSE") + ")");
}
// Overloading with object types
public static void process(Integer id) {
System.out.println("Processing by ID: " + id);
}
public static void process(String name) {
System.out.println("Processing by name: " + name);
}
public static void main(String[] args) {
// Compiler picks exact type match
System.out.println(max(10, 20)); // int version β 20
System.out.println(max(100L, 200L)); // long version β 200
System.out.println(max(1.5f, 2.5f)); // float version β 2.5
System.out.println(max(3.14, 2.71)); // double version β 3.14
// Each call dispatches to the appropriate overload
describe(42); // Integer: 42 (binary: 101010)
describe(3.14); // Double: 3.14 (formatted: 3.14)
describe("Java"); // String: "Java" (length=4)
describe(true); // Boolean: true (TRUE)
process(101); // Processing by ID: 101
process("Alice"); // Processing by name: Alice
}
}Overloading by Order of Parameters
Overloading by changing the order of parameter types is the least common and most error-prone form. While valid, it often leads to confusing call sites where the caller must know the order to determine which version runs. It should be used only when the order genuinely conveys different semantic meaning.
public class OverloadByOrder {
// β
Order matters semantically β different meaning
// Repeats a string N times
public static String repeat(String text, int times) {
return text.repeat(times);
}
// Pads a number to a given width with a character
public static String repeat(int times, String text) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < times; i++) sb.append(text);
return sb.toString();
}
// Another example: log(String message, int code) vs log(int code, String message)
public static void log(String message, int statusCode) {
System.out.println("[" + statusCode + "] " + message);
}
public static void log(int statusCode, String category) {
System.out.println("CATEGORY=" + category + " CODE=" + statusCode);
}
public static void main(String[] args) {
System.out.println(repeat("ha", 3)); // hahaha (String, int)
System.out.println(repeat(3, "ha")); // hahaha (int, String) β same output, different overload
log("Not Found", 404); // [404] Not Found β String, int
log(404, "CLIENT_ERROR"); // CATEGORY=CLIENT_ERROR CODE=404 β int, String
}
}Type Promotion in Method Overloading
When the compiler cannot find an exact type match for a method call, it applies automatic type promotion (widening conversion) to find the closest compatible overload. The promotion chain for numeric primitives is: byte β short β int β long β float β double. A char is first promoted to int and then follows the chain. This happens silently at compile time β the programmer may not realize which overload is actually called.
public class TypePromotion {
public static void show(int x) {
System.out.println("int version: " + x);
}
public static void show(long x) {
System.out.println("long version: " + x);
}
public static void show(double x) {
System.out.println("double version: " + x);
}
// Only long and double overloads exist β no int
public static void display(long x) {
System.out.println("display long: " + x);
}
public static void display(double x) {
System.out.println("display double: " + x);
}
// Only double overload exists
public static void printVal(double x) {
System.out.println("printVal double: " + x);
}
// char promotion demo
public static void check(int x) {
System.out.println("check int: " + x);
}
public static void check(double x) {
System.out.println("check double: " + x);
}
public static void main(String[] args) {
// Exact match β no promotion needed
show(10); // int version: 10
show(10L); // long version: 10
show(10.0); // double version: 10.0
// No int overload β byte/short/int promoted to long (nearest wider type)
byte b = 5;
short s = 10;
display(b); // display long: 5 (byte β long)
display(s); // display long: 10 (short β long)
display(10); // display long: 10 (int β long)
// Only double exists β int, long, float all promote to double
printVal(5); // printVal double: 5.0 (int β double)
printVal(5L); // printVal double: 5.0 (long β double)
printVal(5.0f); // printVal double: 5.0 (float β double)
// char promoted to int first, then follows chain
char c = 'A'; // ASCII value 65
check(c); // check int: 65 (char β int)
// β οΈ Widening vs Autoboxing priority
// Java prefers widening OVER autoboxing
// show(10) β prefers show(int) over show(Integer) if both exist
}
}Return Type & Overloading β Why Return Type Alone Fails
A fundamental rule in Java: return type alone cannot distinguish overloaded methods. If two methods have the same name and the same parameter list but different return types, the compiler reports a duplicate method error β not a valid overload. This is because at the call site, the compiler determines which overload to invoke before it knows what return type the caller expects.
public class ReturnTypeOverloading {
// β INVALID β only return type differs, same parameter list β COMPILE ERROR
// public int convert(String s) { return Integer.parseInt(s); }
// public double convert(String s) { return Double.parseDouble(s); } // ERROR
// WHY? At the call site:
// convert("42") β compiler cannot tell which return type you want
// Even: int x = convert("42") β this is checked AFTER overload resolution
// β
Fix: Use different method names
public static int convertToInt(String s) { return Integer.parseInt(s); }
public static double convertToDouble(String s) { return Double.parseDouble(s); }
// β
Fix: Use different parameter types (then return type can differ too)
public static int parse(String s) { return Integer.parseInt(s); }
public static double parse(String s, boolean asDecimal) {
return Double.parseDouble(s); // Now parameter list differs β valid overload
}
// β
VALID: Same name, different params β return types CAN differ when params differ
public static int getValue(int index) { return index * 10; }
public static String getValue(String key) { return "Value of " + key; }
public static double getValue(double ratio) { return ratio * 100; }
public static void main(String[] args) {
System.out.println(convertToInt("123")); // 123
System.out.println(convertToDouble("3.14")); // 3.14
// Compiler uses parameter type to resolve β return types are fine here
System.out.println(getValue(3)); // 30 (int version)
System.out.println(getValue("score")); // Value of score (String version)
System.out.println(getValue(0.85)); // 85.0 (double version)
}
}Varargs and Method Overloading β The Last Resort
Varargs (type...) can be used in overloaded methods, but they interact with overload resolution in a specific and sometimes surprising way. The Java compiler treats a varargs overload as the last resort β it is chosen only when no non-varargs overload matches the call. This makes varargs a natural 'catch-all' fallback while specific overloads handle common cases efficiently.
public class VarargsOverloading {
// Specific overloads for 1 and 2 args (most common cases β no array overhead)
public static int sum(int a) {
System.out.println("single-arg version");
return a;
}
public static int sum(int a, int b) {
System.out.println("two-arg version");
return a + b;
}
// Varargs β fallback for 0, 3, 4, ... args
public static int sum(int... nums) {
System.out.println("varargs version (" + nums.length + " args)");
int total = 0;
for (int n : nums) total += n;
return total;
}
// Ambiguity example β both could match
public static void print(String s) {
System.out.println("String: " + s);
}
public static void print(Object... objs) {
System.out.println("varargs Object: " + objs.length + " args");
}
public static void main(String[] args) {
System.out.println(sum(5)); // single-arg version β 5
System.out.println(sum(5, 3)); // two-arg version β 8
System.out.println(sum()); // varargs version (0 args) β 0
System.out.println(sum(1, 2, 3)); // varargs version (3 args) β 6
System.out.println(sum(1,2,3,4,5)); // varargs version (5 args) β 15
// Non-varargs preferred over varargs when both match
print("hello"); // String version chosen (more specific than Object...)
print(1, 2); // varargs Object version (no String overload for 2 args)
// β οΈ Ambiguity warning β avoid this pattern
// Two varargs overloads that can both match a specific call:
// sum(int... nums) and sum(Integer... nums) β calling sum(1,2) is ambiguous
}
}Constructor Overloading β Multiple Ways to Build an Object
Constructor overloading applies the same principle β multiple constructors in the same class with different parameter lists. This allows objects to be created in multiple ways: with all fields, with some fields (others defaulting), or from different data sources. The this() call allows one constructor to delegate to another, avoiding code duplication β the same delegation pattern used with method overloads.
public class ConstructorOverloading {
static class Employee {
String name;
String department;
double salary;
boolean isActive;
// Constructor 1: Minimal β only name required
public Employee(String name) {
this(name, "General"); // Delegates to Constructor 2
}
// Constructor 2: Name + department
public Employee(String name, String department) {
this(name, department, 30000.0); // Delegates to Constructor 3
}
// Constructor 3: Name + department + salary
public Employee(String name, String department, double salary) {
this(name, department, salary, true); // Delegates to full constructor
}
// Constructor 4: Full β all fields (all others delegate here)
public Employee(String name, String department, double salary, boolean isActive) {
this.name = name;
this.department = department;
this.salary = salary;
this.isActive = isActive;
}
@Override
public String toString() {
return name + " | " + department + " | βΉ" + salary + " | active=" + isActive;
}
}
public static void main(String[] args) {
Employee e1 = new Employee("Rohit");
System.out.println(e1);
// Output: Rohit | General | βΉ30000.0 | active=true
Employee e2 = new Employee("Sneha", "Engineering");
System.out.println(e2);
// Output: Sneha | Engineering | βΉ30000.0 | active=true
Employee e3 = new Employee("Arjun", "Sales", 55000.0);
System.out.println(e3);
// Output: Arjun | Sales | βΉ55000.0 | active=true
Employee e4 = new Employee("Divya", "HR", 45000.0, false);
System.out.println(e4);
// Output: Divya | HR | βΉ45000.0 | active=false
// β
this() must be the FIRST statement in a constructor
// β Cannot call both this() and super() in same constructor
}
}Overloading vs Overriding β The Most Important Distinction
Method overloading and method overriding are both forms of polymorphism in Java, but they are fundamentally different mechanisms. Overloading is resolved at compile time (static polymorphism). Overriding is resolved at runtime (dynamic polymorphism). Confusing the two is among the most common mistakes in Java interviews and code.
class Animal {
// This method will be OVERRIDDEN in Dog
public void sound() {
System.out.println("Animal makes a sound");
}
// This method will be OVERLOADED in same class
public void eat(String food) {
System.out.println("Animal eats " + food);
}
public void eat(String food, int times) { // OVERLOAD β different params
System.out.println("Animal eats " + food + " " + times + " times");
}
}
class Dog extends Animal {
// β
OVERRIDING β same signature as Animal.sound()
@Override
public void sound() {
System.out.println("Dog barks: Woof!");
}
// β
OVERLOADING inherited method β different parameter list
public void eat(String food, String bowl) {
System.out.println("Dog eats " + food + " from " + bowl);
}
}
public class OverloadingVsOverriding {
public static void main(String[] args) {
Animal a = new Animal();
Dog d = new Dog();
// OVERRIDING β runtime decides based on actual object type
Animal ref = new Dog(); // Animal reference, Dog object
ref.sound(); // Dog barks: Woof! β runtime dispatch β Dog.sound()
// OVERLOADING β compile time decides based on argument types
d.eat("bone"); // Animal eats bone (1-param overload)
d.eat("bone", 3); // Animal eats bone 3 times (2-param overload)
d.eat("bone", "red bowl"); // Dog eats bone from red bowl (Dog overload)
}
}Compile-Time Polymorphism β How the JVM Resolves Overloads
Method overloading is also called compile-time polymorphism or static polymorphism because the binding between a method call and its implementation is resolved during compilation, not at runtime. The Java compiler analyzes the argument types at the call site, applies the overload resolution rules, and emits a direct invokestatic or invokevirtual bytecode instruction pointing to the specific overload. No runtime dispatch table lookup is needed.
public class CompileTimePolymorphism {
static class Printer {
public void print(int value) {
System.out.println("Printing int: " + value);
}
public void print(String value) {
System.out.println("Printing String: " + value);
}
public void print(double value) {
System.out.println("Printing double: " + value);
}
}
public static void main(String[] args) {
Printer p = new Printer();
// Compile time: compiler sees literal 42 β int β picks print(int)
p.print(42); // Printing int: 42
// Compile time: compiler sees literal "Hello" β String β picks print(String)
p.print("Hello"); // Printing String: Hello
// Compile time: compiler sees literal 3.14 β double β picks print(double)
p.print(3.14); // Printing double: 3.14
// β οΈ Reference type vs actual type β overloading uses REFERENCE type!
// This is different from overriding which uses ACTUAL (runtime) type
Object obj = "Java";
p.print(obj.toString()); // print(String) β called via explicit toString()
// Key insight: if print(Object obj) existed:
// p.print(obj) would call print(Object) β because obj's DECLARED type is Object
// NOT print(String) β even though obj holds a String at runtime
// Overloading binds at compile time using DECLARED type, not runtime type
}
}Common Mistakes & Pitfalls β Bugs That Fool Everyone
These mistakes appear consistently in beginner and even intermediate Java code. Each one compiles without error or warning but produces wrong or unexpected behavior at runtime.
// β MISTAKE 1: Assuming return type differentiates overloads
// public int getData() { return 1; }
// public String getData() { return "one"; } // COMPILE ERROR β not an overload
// β MISTAKE 2: Assuming parameter names differentiate overloads
// public void process(int id) { }
// public void process(int code) { } // COMPILE ERROR β same signature
// β MISTAKE 3: Unexpected type promotion picks wrong overload
public class OverloadingMistakes {
static void show(long x) { System.out.println("long: " + x); }
static void show(double x) { System.out.println("double: " + x); }
public static void main(String[] args) {
int i = 100;
show(i); // Prints: long: 100
// Developer expected int or double β gets long via promotion
// Fix: add an int overload, or explicitly cast: show((double)i)
// β MISTAKE 4: Overloading with declared type vs runtime type
// Overloading is resolved at COMPILE TIME using DECLARED type
Object obj = "hello";
// If show(Object x) and show(String x) both existed:
// show(obj) β calls show(Object) β NOT show(String)!
// Because obj is declared as Object, not String
// β MISTAKE 5: null argument β ambiguity between reference-type overloads
// If: static void find(String s) {} and static void find(Integer n) {}
// find(null) β COMPILE ERROR: ambiguous, both String and Integer accept null
// Fix: cast explicitly: find((String) null) or find((Integer) null)
// β MISTAKE 6: Autoboxing creates unexpected overload selection
// If: static void check(int x) {} and static void check(Integer x) {}
// check(5) β calls check(int) β widening preferred over autoboxing
// But if only check(Integer x) exists:
// check(5) β autoboxes 5 to Integer and calls check(Integer)
}
}Bad Practices & Anti-Patterns β What Senior Developers Reject
These overloading anti-patterns are common reasons for failed code reviews and maintenance headaches in professional Java teams. Each one abuses overloading in ways that reduce clarity or cause subtle bugs.
All overloads of a method MUST do the same logical operation on different types or argument counts. 'void process(int id)' that fetches a user and 'void process(String command)' that executes a shell command β same name, completely different behavior. This destroys the principle of least surprise. Rename them: fetchUser(int id) and executeCommand(String cmd).
Defining 6, 7, 8+ overloads of the same method for every possible combination of optional parameters creates an explosion of methods. Readers must scan all overloads to understand the API. Use the Builder pattern or a parameter object (record) instead. A good rule: if you have more than 4 overloads of the same method, consider a builder.
Having both 'void register(String firstName, String lastName)' and 'void register(String lastName, String firstName)' is a disaster β both have the same signature! This won't even compile (both are String, String). Even if the types differ slightly, order-only overloads confuse callers and are trivial to call incorrectly. Use a named object: register(PersonName name).
Adding a varargs overload to a class that already has many specific overloads can cause unexpected resolution. If 'print(String)' exists and you add 'print(Object... objs)', calling print("hello") works β but print((Object)"hello") now goes to varargs. Callers cannot easily predict which version runs without checking the resolution rules.
Designing overloads that intentionally exploit type promotion to route calls β e.g., leaving out an int overload so int calls auto-promote to long β is clever but fragile. A future developer adding an int overload changes the behavior of all existing int callers silently. Always provide the overload for the type you intend to handle rather than relying on promotion.
If you intend to OVERRIDE a parent method but accidentally change the parameter type, Java silently treats it as an OVERLOAD β not an override. The parent method is not replaced; both exist. Using @Override forces the compiler to verify it's a true override. This is the most dangerous interaction between overloading and overriding β always use @Override on intended overrides.
// β ANTI-PATTERN: Missing @Override β silently becomes overload instead of override
class Base {
public void save(Object data) {
System.out.println("Base save");
}
}
class Child extends Base {
// β Intended as override, but parameter type changed to String
// This is an OVERLOAD of save(), NOT an override β Base.save(Object) still exists
public void save(String data) { // No @Override β compiler won't catch this
System.out.println("Child save String");
}
}
// At call site:
Base ref = new Child();
ref.save("hello"); // Calls Base.save(Object) β NOT Child.save(String)!
// Because overloading uses declared type (Base), and Base only has save(Object)
// β
Fix: Always add @Override
class ChildFixed extends Base {
@Override
public void save(Object data) { // Compiler verifies this is a true override
System.out.println("ChildFixed save Object β true override");
}
}Real-World Production Code Examples β Overloading in Context
The following examples model idiomatic Java method overloading patterns found in real enterprise Spring Boot-style codebases, mirroring patterns used in the Java standard library.
package com.techsustainify.notification.service;
import java.util.List;
import java.util.Objects;
public class NotificationService {
/**
* Overloading to provide a simple API with sensible defaults.
* Shortest overloads delegate to the complete implementation.
*/
// Overload 1: Minimal β send to single user with subject only
public void sendEmail(String toAddress, String subject) {
sendEmail(toAddress, subject, "", List.of());
}
// Overload 2: With body text
public void sendEmail(String toAddress, String subject, String body) {
sendEmail(toAddress, subject, body, List.of());
}
// Overload 3: Full β complete implementation (all others delegate here)
public void sendEmail(String toAddress, String subject,
String body, List<String> ccAddresses) {
Objects.requireNonNull(toAddress, "Recipient address must not be null");
Objects.requireNonNull(subject, "Email subject must not be null");
System.out.println("TO: " + toAddress);
System.out.println("SUBJECT: " + subject);
if (!body.isBlank())
System.out.println("BODY: " + body);
if (!ccAddresses.isEmpty())
System.out.println("CC: " + ccAddresses);
System.out.println("--- Email sent ---");
}
/**
* Overloading by type β send SMS to a single number or multiple recipients.
*/
public void sendSMS(String phoneNumber, String message) {
System.out.println("SMS to " + phoneNumber + ": " + message);
}
// Varargs overload β broadcast to multiple numbers
public void sendSMS(String message, String... phoneNumbers) {
System.out.println("Broadcasting SMS to " + phoneNumbers.length + " recipients");
for (String phone : phoneNumbers) {
sendSMS(phone, message); // Delegates to single-recipient overload
}
}
/**
* Constructor overloading β different initialization paths.
*/
static class Alert {
String title;
String message;
String severity;
String recipient;
public Alert(String title, String message) {
this(title, message, "INFO");
}
public Alert(String title, String message, String severity) {
this(title, message, severity, "admin@example.com");
}
public Alert(String title, String message, String severity, String recipient) {
this.title = title;
this.message = message;
this.severity = severity;
this.recipient = recipient;
}
@Override
public String toString() {
return "[" + severity + "] " + title + " β " + recipient;
}
}
public static void main(String[] args) {
NotificationService svc = new NotificationService();
svc.sendEmail("user@example.com", "Welcome!");
svc.sendEmail("user@example.com", "Invoice", "Please find your invoice attached.");
svc.sendEmail("user@example.com", "Report", "Monthly report",
List.of("manager@example.com", "cfo@example.com"));
svc.sendSMS("+919876543210", "Your OTP is 482910");
svc.sendSMS("System downtime at 2 AM",
"+911111111111", "+912222222222", "+913333333333");
System.out.println(new Alert("Disk Full", "Server disk at 95%"));
System.out.println(new Alert("CPU Spike", "CPU > 90%", "CRITICAL"));
System.out.println(new Alert("Login Fail", "5 failed attempts", "WARNING", "sec@example.com"));
}
}package com.techsustainify.util;
/**
* Utility class demonstrating JDK-style method overloading.
* Mirrors patterns in java.lang.Math and java.util.Objects.
*/
public class MathUtils {
// Overloaded clamp β constrains a value to [min, max] range
// Works for all numeric types just like JDK Math.clamp (Java 21)
public static int clamp(int value, int min, int max) {
return Math.max(min, Math.min(max, value));
}
public static long clamp(long value, long min, long max) {
return Math.max(min, Math.min(max, value));
}
public static float clamp(float value, float min, float max) {
return Math.max(min, Math.min(max, value));
}
public static double clamp(double value, double min, double max) {
return Math.max(min, Math.min(max, value));
}
// Overloaded round β to different decimal places
public static double round(double value) {
return round(value, 0);
}
public static double round(double value, int decimalPlaces) {
double scale = Math.pow(10, decimalPlaces);
return Math.round(value * scale) / scale;
}
// Overloaded average β varargs for any count of values
public static double average(int first, int... rest) {
int sum = first;
for (int v : rest) sum += v;
return (double) sum / (rest.length + 1);
}
public static double average(double first, double... rest) {
double sum = first;
for (double v : rest) sum += v;
return sum / (rest.length + 1);
}
public static void main(String[] args) {
System.out.println(clamp(150, 0, 100)); // 100 (int)
System.out.println(clamp(-5.0, 0.0, 1.0)); // 0.0 (double)
System.out.println(round(3.14159)); // 3.0
System.out.println(round(3.14159, 2)); // 3.14
System.out.println(round(3.14159, 4)); // 3.1416
System.out.println(average(10, 20, 30)); // 20.0 (int varargs)
System.out.println(average(1.5, 2.5, 3.5, 4.5)); // 3.0 (double varargs)
}
}Overloading Resolution Flowchart β How the Compiler Picks an Overload
This flowchart shows the exact steps the Java compiler takes when resolving which overloaded method to invoke for a given call.
Code Execution Flow β from source to output
Java Method Overloading Interview Questions β Beginner to Advanced
These questions are consistently asked in Java fresher interviews, OCPJP/OCA certification exams, and campus placement tests.
Practice Questions β Test Your Overloading Knowledge
Challenge yourself with these practice questions. Attempt each independently before reading the answer β active recall is proven to be 2β3x more effective than passive reading.
1. Which overload is called? What is the output? static void print(int x) { System.out.println("int: " + x); } static void print(double x) { System.out.println("double: " + x); } public static void main(String[] args) { print(10); print(10.0); print(10L); byte b = 5; print(b); }
Easy2. Will this compile? If yes, what prints? static int compute(int a, int b) { return a + b; } static double compute(int a, int b) { return a * 1.0 + b; } public static void main(String[] args) { System.out.println(compute(2, 3)); }
Easy3. Identify which version is called and explain: static void display(Object o) { System.out.println("Object"); } static void display(String s) { System.out.println("String"); } public static void main(String[] args) { Object obj = "Hello"; display(obj); display("Hello"); display(null); }
Medium4. Write an overloaded method 'format' that: (a) takes a double and returns it rounded to 2 decimal places as a String, (b) takes a double and an int (decimal places) and formats accordingly, (c) takes a String and an int (max length) and truncates if longer.
Medium5. What is the output and why? static void test(Integer x) { System.out.println("Integer"); } static void test(long x) { System.out.println("long"); } public static void main(String[] args) { int i = 5; test(i); }
Medium6. Refactor this code to use proper constructor overloading with delegation: class Product { String name; double price; String category; boolean inStock; Product(String name, double price, String category, boolean inStock) { this.name = name; this.price = price; this.category = category; this.inStock = inStock; } } // Callers often need: new Product("Book", 299.0, "Education", true) // But also want: new Product("Book", 299.0) with defaults
Medium7. What is the output? class Parent { public void show(Object o) { System.out.println("Parent Object"); } } class Child extends Parent { public void show(String s) { System.out.println("Child String"); } } public class Test { public static void main(String[] args) { Parent p = new Child(); p.show("hello"); p.show(new Object()); } }
Hard8. Predict the output: static void go(int x, long y) { System.out.println("int,long"); } static void go(long x, int y) { System.out.println("long,int"); } public static void main(String[] args) { go(1, 2); }
HardConclusion β Method Overloading: Compile-Time Polymorphism Done Right
Method overloading is the foundation of clean, intuitive Java APIs. It allows a single logical operation to work across different data types and argument counts under one recognizable name β eliminating the need for type-encoded names like addInt, addDouble, or addWithDefault. The Java standard library is built on this principle: System.out.println, Math.max, String.valueOf are all overloaded for every type you'll ever need.
Mastering overloading means understanding not just the syntax, but the resolution rules: exact match first, then widening, then autoboxing, then varargs. It means knowing that declared type, not runtime type, governs overload selection. It means recognizing when overloading is the right tool (type-variant operations, optional parameters) versus when it introduces confusion (inconsistent semantics, too many overloads, order-based disambiguation).
Your next step: Java Method Return Types β where you'll explore how methods communicate results back to callers, void vs typed returns, returning multiple values with records, and how return types interact with overriding and polymorphism. β