skip to content

How do varargs participate in overload resolution? Describe the phases the compiler uses to pick a method.

level: seniorimportance: should knowfreq 55%

answer

  1. 3 phases: strict → boxing → varargs
  2. Varargs is the LAST resort (phase 3)
  3. Fixed-arity always beats varargs
  4. Boxing (phase 2) beats varargs (phase 3)
  5. Two equal varargs = ambiguous compile error

basics

~10 s

When choosing between overloaded methods, the compiler tries non-varargs (fixed-arity) matches first. Only if none fit does it consider varargs methods. So a more specific fixed overload always beats a varargs one.

solid answer

~50 s

Java resolves overloaded calls in three phases, and varargs come last. Phase 1 matches without boxing/unboxing or varargs. Phase 2 allows autoboxing/unboxing but still no varargs. Phase 3 finally allows varargs. The compiler picks the most specific applicable method in the earliest phase that has a match; it never falls through to a later phase if an earlier one succeeds. This means a fixed-arity overload like print(int, int) is chosen over print(int...) for a two-argument call, because the fixed method is applicable in phase 1 while the varargs method only qualifies in phase 3. The practical consequences: varargs is the lowest-priority match, so adding a fixed overload silently 'steals' calls from a varargs one; and an exact-count fixed overload always wins. Ambiguity errors arise when two varargs methods are equally applicable and neither is more specific.

code

java · 11 lines
java
static void p(int a, int b) { System.out.println("fixed(int,int)"); }
static void p(int... a)      { System.out.println("varargs"); }

p(1, 2);  // "fixed(int,int)"  -> phase 1 wins
p(1);     // "varargs"         -> no 1-arg fixed overload
p();      // "varargs"         -> empty array

// Boxing (phase 2) outranks varargs (phase 3):
static void m(Integer x) { System.out.println("boxing"); }
static void m(int... x)  { System.out.println("varargs"); }
// m(5) prints "boxing"

go deeper

for a junior

Knows that an exact, fixed-argument method is chosen over a varargs one — varargs is a fallback.

for a middle

Can state that varargs is the last-resort match and that adding a fixed overload changes which method a call hits.

for a senior

Names the three phases (strict, loose/boxing, varargs), explains earliest-phase-then-most-specific selection, and identifies the boxing-beats-varargs ordering and ambiguity cases.

for a principal

Reasons about API evolution risk: mixing fixed and varargs overloads creates silent behavior shifts; advocates clear overload-set design and explicit casts for null/array disambiguation.

## Why this matters When you call an overloaded method, the compiler must pick exactly one target at compile time. Varargs complicates this because a varargs method can match almost any argument count. To keep behavior predictable, the Java Language Specification (JLS §15.12.2) resolves overloads in **three ordered phases**, and **varargs is deliberately the last resort**. ## The three phases The compiler considers methods in this order and stops at the first phase that yields one or more *applicable* methods: 1. **Phase 1 — Strict invocation.** Match using subtyping only: **no autoboxing/unboxing, no varargs.** Each argument must be assignable to the corresponding parameter via widening reference/primitive conversion. 2. **Phase 2 — Loose invocation.** Same as phase 1 but now **autoboxing and unboxing are allowed** (e.g. `int` → `Integer`). Still **no varargs.** 3. **Phase 3 — Variable-arity invocation.** Now **varargs methods become applicable**: the trailing loose arguments are gathered into the array. If phase 1 produces any applicable method, the compiler chooses among *only* those — it never even looks at varargs. Within the chosen phase, if several methods apply, the compiler then picks the **most specific** one (the one whose parameter types are subtypes of the others'). If no single most-specific method exists, you get an **ambiguous method call** compile error. ## The headline rule: fixed-arity beats varargs Because varargs only enters in phase 3, **any applicable fixed-arity overload wins over a varargs overload.** Example: ```java void p(int a, int b) { System.out.println("fixed"); } void p(int... a) { System.out.println("varargs"); } p(1, 2); // prints "fixed" — phase-1 fixed match wins p(1); // prints "varargs" — no fixed 1-arg overload, falls to phase 3 p(); // prints "varargs" — empty array ``` This is why people say varargs has the **lowest priority** in resolution. ## Boxing also outranks varargs Phase 2 (boxing) runs *before* phase 3 (varargs). So given: ```java void m(Integer x) { ... } // fixed, needs boxing void m(int... x) { ... } // varargs m(5); // calls m(Integer) — boxing (phase 2) beats varargs (phase 3) ``` ## Ambiguity between two varargs methods If two varargs methods are both applicable in phase 3 and neither is more specific, the call is ambiguous: ```java void q(Object... o) { ... } void q(String... s) { ... } q(); // ambiguous — both apply, neither more specific (compile error) ``` (`q("a")` is *not* ambiguous: `String...` is more specific than `Object...`.) ## The 'silent steal' pitfall Because a fixed overload always wins, **adding a fixed-arity overload later can silently redirect existing calls** away from a varargs method, changing behavior without any call-site change. This is a real maintenance hazard — overload sets mixing fixed and varargs methods should be designed carefully. ## The null / array ambiguity A related gotcha: `m(null)` to `m(Object...)` is ambiguous about whether `null` is the whole array or one element; the compiler treats `null` as the array reference (with a warning). Cast explicitly — `m((Object) null)` vs `m((Object[]) null)` — to disambiguate. ## Summary Three phases: strict → loose (boxing) → varargs. Earliest applicable phase wins; within it, the most specific method wins or it's ambiguous. Varargs is last, so fixed-arity and even boxing-required overloads beat it.

  • Given void f(int x) and void f(int... x), what does f(1) call and why?
    It calls f(int x). The fixed-arity overload is applicable in phase 1 (strict, no varargs), so the compiler chooses it before ever reaching the varargs phase.
  • Why is m(null) ambiguous or warned when m(Object...) is the only overload?
    null is assignment-compatible with both the array type Object[] and a single Object element, so the compiler can't tell if you mean an empty/whole-array null or one null element. It treats null as the array and warns; cast to disambiguate.

saying these in an interview costs you the question

  • Claiming the compiler prefers varargs because it 'matches more cases'
  • Saying boxing and varargs are considered in the same phase
  • Asserting overload resolution happens at runtime
  • Believing print(int,int) and print(int...) for two args is ambiguous (the fixed one wins)

context