6.3 Method Overloading Principles

Key Takeaways

  • Method overloading requires methods within the same class to share the exact same identifier while having distinct formal parameter lists differing by count, data types, or type order.
  • Changing only the return type, access modifier, parameter variable names, or declared throws clause does not overload a method and results in a fatal duplicate method compile error.
  • The Java compiler resolves overloaded method calls at compile time using a strict four-phase hierarchy: exact match, primitive widening, autoboxing/unboxing, and variable-arity (varargs).
  • Primitive widening takes precedence over autoboxing, and the compiler strictly prohibits combining primitive widening with autoboxing in a single method argument conversion.
  • Overloaded methods with symmetric parameter widening conversions cause a fatal compile-time ambiguity error when invoked with arguments that match multiple signatures equally.
Last updated: September 2026

6.3 Method Overloading Principles

[!NOTE] Exam Focus: Method overloading represents Oracle's "create overloaded methods" objective, and one of the most heavily emphasized ideas on the 1Z0-811 examination. Candidates must be prepared to determine whether a group of method declarations constitutes legal overloading or an illegal duplicate method error, trace compiler resolution across primitive widening and autoboxing hierarchies, recognize varargs resolution rules, and spot compiler ambiguity errors.

Java provides multiple mechanisms for implementing polymorphism—the object-oriented principle that allows a single interface or identifier to represent different underlying behaviors. While method overriding represents runtime dynamic polymorphism based on inheritance, method overloading represents compile-time static polymorphism (also referred to as ad-hoc polymorphism). Method overloading enables a class to expose multiple operations under a unified, intuitive method name tailored to different parameter counts and data types.


What Constitutes Valid Method Overloading

Under JLS §8.4.9, method overloading occurs when two or more methods within the same class (or inherited from a superclass) share the exact same method identifier but possess different formal parameter lists.

Valid Overloading Criteria

To successfully overload a method, the parameter list must differ in at least one of three distinct ways:

  1. Different Number of Parameters:
    public void printReport(String title) { }
    public void printReport(String title, int copies) { } // LEGAL: Different parameter count
    
  2. Different Data Types of Parameters:
    public void process(int code) { }
    public void process(String code) { } // LEGAL: Different parameter types
    
  3. Different Order of Parameter Data Types:
    public void logEntry(int code, String message) { }
    public void logEntry(String message, int code) { } // LEGAL: Different parameter type order
    

What Does NOT Constitute Method Overloading

A method's signature consists strictly of its identifier and its ordered formal parameter types. Consequently, altering any other component of a method declaration while leaving the name and parameter types identical does not overload the method and results in a fatal compile-time error (method ... is already defined):

Attempted VariationCode Pair ExampleCompiler Result
Changing Return Type Onlypublic int compute(int x)<br/>public double compute(int x)COMPILE ERROR: duplicate method compute(int)
Changing Access Modifier Onlypublic void send(String s)<br/>private void send(String s)COMPILE ERROR: duplicate method send(String)
Changing Parameter Names Onlyvoid draw(int width, int height)<br/>void draw(int x, int y)COMPILE ERROR: duplicate method draw(int, int)
Changing Exception Specification Onlyvoid read() throws IOException<br/>void read() throws SQLExceptionCOMPILE ERROR: duplicate method read()
Changing static Modifier Onlypublic static void run(int n)<br/>public void run(int n)COMPILE ERROR: duplicate method run(int)

Rationale: Why Return Types Cannot Overload Methods

Consider what would happen if Java allowed overloading by return type alone:

// HYPOTHETICAL INVALID CODE
public int getScore() { return 100; }
public double getScore() { return 100.0; }

When a caller invokes the method as a freestanding statement without capturing the result:

getScore();

Because the caller does not assign the result to any typed variable, the compiler would have no syntactic or semantic clue whether the caller intended to invoke the int or double variant. To prevent this inherent ambiguity, return types are completely excluded from method signatures and overload resolution.


Overload Resolution Hierarchy

When an overloaded method is invoked, the Java compiler determines which method version to bind at compile time using a deterministic, multi-tier resolution algorithm defined in JLS §15.12.2:

Invocation Call: e.g., execute(10)
   │
   ▼
Phase 1: Exact Parameter Type Match (Identity Conversion)
   │ (If no matching method is found)
   ▼
Phase 2: Primitive Widening Conversion (e.g., int -> long -> float -> double)
   │ (If no matching method is found)
   ▼
Phase 3: Autoboxing / Unboxing Conversion (e.g., int -> Integer)
   │ (If no matching method is found)
   ▼
Phase 4: Variable-Arity (Varargs) Invocation (e.g., int...)
   │ (If no matching method is found)
   ▼
Compilation Error: No suitable method found

Tracing Resolution Precedence

Consider this comprehensive test class:

public class ResolutionOrder {
    public static void display(double d) { System.out.println("Widening (double)"); }
    public static void display(Integer i) { System.out.println("Autoboxing (Integer)"); }
    public static void display(int... v)  { System.out.println("Varargs (int...)"); }

    public static void main(String[] args) {
        int value = 42;
        display(value); // Which method is selected by the compiler?
    }
}
  1. Phase 1 (Exact Match): The compiler looks for display(int). None exists.
  2. Phase 2 (Widening): The compiler checks if int can be widened. Primitive widening allows int to widen to long, float, or double. Since display(double) is declared, it matches in Phase 2!
  3. The program prints: Widening (double).
  4. Autoboxing to Integer is not evaluated because Phase 2 succeeded. Varargs is evaluated only if all previous phases fail.

Critical Resolution Constraints and Edge Cases

1. Primitive Widening Beats Autoboxing

As demonstrated above, Java prioritizes primitive widening over boxing conversion. Widening was built into the language in Java 1.0 to preserve backward compatibility, whereas autoboxing was added in Java SE 5.

2. Prohibited Combined Conversion: Widening Followed by Boxing

While Java allows widening followed by subtyping (e.g., int widening to long and then passing to a parameter expecting long), the compiler strictly forbids primitive widening followed by autoboxing in method invocation conversions:

public class WideningBoxingTrap {
    public static void process(Long obj) {
        System.out.println("Long object: " + obj);
    }

    public static void main(String[] args) {
        // COMPILE ERROR: incompatible types: int cannot be converted to Long
        process(10); 
        
        // LEGAL: 10L is a long literal, which boxes directly to Long
        process(10L); 
    }
}

In the code above, 10 is an int. For process(10) to match process(Long), the int would first have to widen to long and then box to Long. Java does not perform this compound conversion.

3. Permitted Combined Conversion: Boxing Followed by Widening (to Object/Number)

Conversely, Java does permit autoboxing followed by reference widening to a superclass or interface:

public static void examine(Object obj) { System.out.println("Object accepted"); }

// Calling examine with primitive int:
examine(100); // LEGAL! int boxes to Integer, and Integer widens (subtypes) to Object!

4. Varargs as the Choice of Last Resort

Methods declaring variable-arity parameters (...) are relegated to Phase 4. If any fixed-arity method matches via exact match, widening, or boxing, the compiler selects the fixed-arity method:

public static void test(int a, int b) { System.out.println("Fixed-arity"); }
public static void test(int... nums)  { System.out.println("Varargs"); }

test(1, 2); // Prints: Fixed-arity!
test(1, 2, 3); // Prints: Varargs!

Compiler Ambiguity Errors

When the compiler finds two or more overloaded methods that match an invocation equally well without one being strictly more specific than the other, it cannot guess the programmer's intent and raises a compile-time error: reference to ... is ambiguous.

1. Symmetric Primitive Widening Ambiguity

public class AmbiguityDemo {
    public static void add(int a, double b) {
        System.out.println("int, double");
    }
    public static void add(double a, int b) {
        System.out.println("double, int");
    }

    public static void main(String[] args) {
        // COMPILE ERROR: reference to add is ambiguous
        add(5, 5); 
    }
}

Why Compilation Fails: The argument pair (5, 5) consists of two int literals. For the first method add(int, double), the second argument must widen (int -> double). For the second method add(double, int), the first argument must widen (int -> double). Because each method requires exactly one widening conversion, neither method is more specific. The compiler rejects the code.

To resolve this ambiguity, the caller must explicitly cast one argument:

add(5, (double) 5); // Clearly invokes add(int, double)

2. Null Reference Ambiguity Between Overloaded Object Types

Another classic exam scenario involves passing null to overloaded methods taking unrelated object types:

public class NullAmbiguity {
    public static void inspect(String s) { System.out.println("String"); }
    public static void inspect(Integer i) { System.out.println("Integer"); }

    public static void main(String[] args) {
        // COMPILE ERROR: reference to inspect is ambiguous
        inspect(null); 
    }
}

Because null is a valid literal for both String and Integer, and neither type inherits from the other, the compiler cannot determine which method takes priority and generates a compile-time error. If one type were a subtype of the other (e.g., String and Object), the compiler would choose the most specific type (String).

Loading diagram...
Compiler Overload Resolution Decision Hierarchy
Test Your Knowledge

Which of the following method declarations represents a legally valid overload of the method:

public int calculate(int x, double y)

A
B
C
D
Test Your Knowledge

Consider the following class with three overloaded methods:

public class OverloadPrecedence {
    public static void check(long val) { System.out.println("long"); }
    public static void check(Integer val) { System.out.println("Integer"); }
    public static void check(int... val) { System.out.println("varargs"); }

    public static void main(String[] args) {
        int num = 10;
        check(num);
    }
}
What is printed to the console upon running this program?

A
B
C
D
Test Your Knowledge

What is the result of attempting to compile and execute the following Java class?

public class AmbiguityTest {
    public static void combine(int a, double b) {
        System.out.println("int-double");
    }
    public static void combine(double a, int b) {
        System.out.println("double-int");
    }
    public static void main(String[] args) {
        combine(10, 20);
    }
}

A
B
C
D