skip to content

What causes an "ambiguous method call" compile error with overloaded methods, and how do you resolve it?

level: middleimportance: should knowfreq 48%

answer

  1. Two applicable methods, neither most-specific
  2. null + unrelated reference types is the classic case
  3. Most specific = params assignable to the other's
  4. Fix by casting the argument
  5. Compile-time error, not runtime

basics

~20 s

It happens when, in a given resolution phase, two or more overloads are equally applicable to your arguments and neither is more specific than the other, so the compiler cannot decide. You fix it by casting an argument to pin the intended overload.

solid answer

~50 s

An ambiguity error arises when overload resolution finds two (or more) applicable methods within the same phase but cannot select a single "most specific" one. A classic trigger is passing null to two overloads with unrelated reference types, e.g. f(String) and f(Integer) — null fits both and neither parameter type is a subtype of the other, so neither is more specific. Another is mixing autoboxing and widening so two candidates tie in phase 2, or calling f(1, 2) when both f(long, int) and f(int, long) exist. The fix is to make your intent explicit: cast the argument to the exact parameter type, e.g. f((String) null), or restructure the overloads so one is unambiguously more specific. You can also rename one method to remove the overload entirely. Note that ambiguity is a compile-time error, not a runtime one, so it surfaces immediately during build.

code

java · 10 lines
java
void f(String s)  { System.out.println("String"); }
void f(Integer i) { System.out.println("Integer"); }

// f(null);              // COMPILE ERROR: reference to f is ambiguous
f((String) null);        // OK -> "String"  (cast pins the overload)

// Not ambiguous when one type is more specific:
void g(Object o) { System.out.println("Object"); }
void g(String s) { System.out.println("String"); }
g(null);                 // OK -> "String" (String is more specific than Object)

go deeper

for a junior

Recognizes that some overloaded calls fail to compile and that a cast can help, even without the precise rule.

for a middle

Explains the most-specific rule, gives the null-with-two-reference-types example, and resolves it with a cast.

for a senior

Diagnoses ambiguity across widening/boxing/varargs interactions and recommends API-level fixes over casts.

for a principal

Treats pervasive ambiguity as an API-design smell, sets team guidance (prefer distinct names, avoid overlapping reference overloads) and reasons about generics/erasure-induced ambiguities.

### The root cause: no single most-specific method Java's overload resolution (see the three-phase algorithm) ends each phase by choosing the **most specific** applicable method. Method A is **more specific** than method B if every argument A accepts would also be accepted by B — informally, A's parameter types are assignable to B's. The winner is the one method more specific than all others. **Ambiguity** occurs when, within the phase that first finds applicable methods, **two methods are mutually applicable but neither is more specific than the other**. The compiler refuses to guess and emits `reference to f is ambiguous`. ### Trigger 1 — null with unrelated reference types ``` void f(String s) {} void f(Integer i) {} f(null); // AMBIGUOUS ``` `null` is assignable to both `String` and `Integer`. Neither `String` nor `Integer` is a subtype of the other, so neither overload is more specific. **Fix:** cast — `f((String) null)` pins `f(String)`. Contrast: `void f(Object o); void f(String s); f(null);` is **not** ambiguous, because `String` *is* more specific than `Object` (String is assignable to Object), so `f(String)` wins. ### Trigger 2 — widening vs boxing tie ``` void g(long x) {} void g(Integer x) {} g(5); // NOT ambiguous -> g(long) (widening in phase 1 beats boxing in phase 2) ``` But: ``` void g(Long x) {} void g(Integer x) {} g(5); // AMBIGUOUS in phase 2? Actually compile error: neither applies in phase 1; in phase 2, 5 boxes to Integer only -> g(Integer) wins. ``` The genuinely tricky ties come from competing reference conversions of equal rank, e.g. two interfaces a class implements. ### Trigger 3 — symmetric parameter lists ``` void h(int a, long b) {} void h(long a, int b) {} h(1, 2); // AMBIGUOUS ``` For `h(1, 2)` (both `int`), candidate one needs widening on the **second** arg, candidate two needs widening on the **first**. Neither method's parameter list is assignable to the other's, so neither is more specific. **Fix:** cast one argument — `h(1, 2L)` selects `h(int, long)`. ### Trigger 4 — generics erasure / inference ties Generic overloads can collide after type inference yields equally-specific candidates; the remedy is the same: provide explicit type witnesses or casts. ### How to resolve ambiguity (toolbox) 1. **Cast the argument** to the exact parameter type of the overload you want: `f((Integer) null)`. This is the most common, surgical fix. 2. **Add an exact-match overload** so one candidate becomes strictly more specific (e.g. add `f(String)` next to `f(Object)`). 3. **Avoid the design** that causes it: don't create overloads that differ only in ways callers cannot disambiguate naturally — prefer **distinct method names** (`fromString` / `fromInt`) over collision-prone overloads. 4. **Use a typed local variable**: assign the argument to a variable of the desired type first, then pass it — the variable's declared type drives resolution. ### Important properties - Ambiguity is a **compile-time** error — you find out at build time, never at runtime. - It depends on the **static (declared) types** of arguments, not their runtime values. - It is symptomatic of an **API smell**: heavily-overloaded methods with overlapping applicable types are hard to use; the cleanest fix is often a clearer API, not a cast.

  • Why is f(null) ambiguous for f(String)/f(Integer) but not for f(Object)/f(String)?
    With String and Integer neither type is a subtype of the other, so neither overload is more specific. With Object and String, String is assignable to Object, so f(String) is strictly more specific and wins — no ambiguity.
  • Does an ambiguous call fail at compile time or runtime?
    Compile time. Overload resolution is performed by the compiler from the static argument types, so the build fails before any code runs.

Like asking two equally-qualified specialists to take a case where neither outranks the other — without a tie-breaker the front desk refuses to assign it. You break the tie by explicitly naming the doctor you want (the cast).

saying these in an interview costs you the question

  • Thinking ambiguity is a runtime exception
  • Believing declaration order or alphabetical order breaks the tie
  • Assuming the JVM picks the 'closest' type automatically
  • Confusing it with overriding ambiguity

context