Method overloading lets a single method name handle several parameter lists, so calculate(5), calculate(2.5) and calculate("3") can all exist side by side without inventing three separate names. Java picks the right one for you at compile time, as long as the overloads differ enough for the choice to be unambiguous.
What is method overloading?
Overloaded methods share a name and live in the same class, but each one takes a different set of parameter types. The number of parameters can differ, the types can differ, or both can differ.
public class OverloadBasics {
static void print(int value) {
System.out.println("int: " + value);
}
static void print(double value) {
System.out.println("double: " + value);
}
static void print(String value) {
System.out.println("String: " + value);
}
static void print(String label, int value) {
System.out.println(label + " = " + value);
}
public static void main(String[] args) {
print(42);
print(3.5);
print("hello");
print("count", 7);
}
}Four calls, four different overloads, and the output shows which one was chosen each time:
int: 42
double: 3.5
String: hello
count = 7The last overload takes two parameters, which is a different shape from the single-parameter ones. Two methods with the same name and the same parameter types would be a compile error, so the signatures have to be genuinely distinguishable.
How does Java choose between overloads?
The compiler looks at the argument types and picks the most specific overload that accepts them without a conversion that loses information. Exact matches win, then widening conversions such as int to double, then boxing into a wrapper object as a last resort.
public class OverloadResolution {
static void show(int n) {
System.out.println("int version");
}
static void show(long n) {
System.out.println("long version");
}
static void show(double n) {
System.out.println("double version");
}
public static void main(String[] args) {
show(7);
show(7L);
show(7.5);
show('c');
}
}The output is:
int version
long version
double version
int versionEach literal matched the closest overload: a plain int, a long literal, a double literal, and finally a char, which widens to int rather than to double.
Why does an int argument skip the Integer overload?
When two overloads are both possible, the one that needs no conversion at all beats the one that needs boxing. Widening a primitive to a bigger primitive is treated as a cheaper conversion than turning it into an object, so the int version always wins over an Integer version for an int argument.
public class TrapWidening {
static void show(int n) {
System.out.println("int version");
}
static void show(Integer n) {
System.out.println("Integer version");
}
public static void main(String[] args) {
show(5);
}
}The output is int version, even though the Integer overload was a legal choice. This matters when you are reading code written by someone else: if a method prints the int version when you expected the object version, the argument type is the reason, not a bug in your code.
What is a varargs parameter?
A parameter declared with three dots accepts any number of arguments of that type, including none at all. It arrives inside the method as an array.
public class Varargs {
static int total(int... numbers) {
int sum = 0;
for (int n : numbers) {
sum += n;
}
return sum;
}
public static void main(String[] args) {
System.out.println(total());
System.out.println(total(5));
System.out.println(total(5, 10, 15));
}
}The output is:
0
5
30Varargs are the reason a single method can cover a case with no arguments, one argument or twenty. One rule catches people out: a varargs parameter has to be the last one in the list, because the compiler collects everything after it into the array.
Why can two methods not differ only in their return type?
Because the return type is not part of the signature the compiler matches on. If two methods had the same name and the same parameters but different return types, every call would be ambiguous, so Java rejects it. Give the methods different names instead.
public class DifferentReturns {
static int parseWhole(String text) {
return Integer.parseInt(text);
}
static double parseDecimal(String text) {
return Double.parseDouble(text);
}
public static void main(String[] args) {
System.out.println(parseWhole("42") + 1);
System.out.println(parseDecimal("2.5") * 2);
}
}The output is 43 and 5.0. The names carry the information that the return type cannot, which is often clearer for a reader anyway.
When is overloading a good idea, and when should you rename instead?
Overloading works well when the different versions are genuinely the same idea applied to different types: a print method that handles an int, a double and a String is one concept, and the name should say so. It also works for a summary method that can take a value or a value and a unit.
Overloading works badly when the versions do different jobs. If process() formats a date in one version and sends an email in the other, the shared name hides the difference and every reader has to check the parameter types to know which one is called. In that case two clear names such as formatDate() and sendEmail() are better than a clever overload. The same rule applies to a method that takes a boolean to switch between two behaviours: make that an enum or two separate methods instead.
Overloading rules at a glance
| Rule | What it means |
|---|---|
| Signatures must differ | Same name plus same parameter types is a compile error |
| Return type does not count | Two overloads cannot differ only by what they return |
| Most specific wins | Exact match, then widening, then boxing |
| Varargs go last | A varargs parameter has to be the final one |
| Same name, same class | Overloads live together so the caller sees one name |
| Keep the set small | Many overloads of one name are hard to tell apart at the call site |
Try it yourself: write a class with overloaded area methods: one for a square, one for a rectangle, and one that takes an array of sides and adds them up. Call each with matching arguments and confirm the right one runs. Then add an overload that takes a boolean to switch between a square and a rectangle and feel how the call site becomes unclear, before renaming it into two separate methods.