Definition
The overload resolution rule:
- null is assignable to any reference type. Both
process(Object)andprocess(String)accept null. - Java picks the most specific match. String extends Object, so String is more specific.
- Result:
process(null)callsprocess(String). - Ambiguity: If neither type is a subtype of the other (e.g., String and Integer), the call does not compile.
A bank form with two acceptance windows: one for "any document" (Object) and one for "passport only" (String). When you bring a null document, the bank sends you to the more specific window (passport) because it is a narrower match.
Understanding the Concepts
What happens with Object and String overloads?
public class Dispatcher {
public static void process(Object o) { System.out.println("Object: " + o); }
public static void process(String s) { System.out.println("String: " + s); }
public static void main(String[] args) {
process(null); // "String: null" – String is more specific
process("hello"); // "String: hello" – exact match
process((Object) null); // "Object: null" – cast forces Object overload
process(42); // "Object: 42" – int autoboxed to Integer, only Object matches
}
}
null is assignable to both Object and String. The compiler picks String because it is a subclass of Object (more specific).
What happens with two equally specific types?
public static void process(String s) { System.out.println("String"); }
public static void process(Integer i) { System.out.println("Integer"); }
// process(null); // COMPILE ERROR: ambiguous
// Neither String nor Integer is a subtype of the other
// Fix: cast to the desired type
process((String) null); // "String"
process((Integer) null); // "Integer"
What about three overloads: Object, String, CharSequence?
public static void process(Object o) { System.out.println("Object"); }
public static void process(CharSequence cs) { System.out.println("CharSequence"); }
public static void process(String s) { System.out.println("String"); }
process(null); // "String" – String is the most specific (String extends CharSequence extends Object)
The compiler walks the type hierarchy and picks the most specific type that null could be.
How does this work with varargs?
public static void process(Object o) { System.out.println("Object"); }
public static void process(String... args) { System.out.println("Varargs"); }
process(null); // "Object" – non-varargs is preferred over varargs
process((String[]) null); // "Varargs" – explicit cast to String[]
Overload resolution prefers non-varargs over varargs when both match.
What are the resolution rules in order?
- Phase 1: Find exact matches without boxing or varargs.
- Phase 2: Allow autoboxing/unboxing but no varargs.
- Phase 3: Allow varargs.
- Among matches: Pick the most specific type.
- If tied: Compile error (ambiguous).
What are common mistakes?
- Assuming null goes to Object. It goes to the most specific type, which is usually not Object.
- Overloading with sibling types (String, Integer). Calling with null is ambiguous. Avoid this design.
- Forgetting that varargs has lowest priority. Non-varargs matches are tried first.
What should a senior engineer know?
- This is JLS Section 15.12.2. The "most specific method" rule is formally defined in the Java Language Specification.
- IDE support: IntelliJ shows which overload is selected when you hover over a method call. Use it to verify.
- Avoid overloading with null-ambiguous signatures. If two overloads can both match null, the API is confusing. Use different method names instead.
Visual Overview
process(Object o) process(String s)
│ │
└──── null ────────────┘
│
▼
Both match. String is more specific than Object.
→ calls process(String)
process(String s) process(Integer i)
│ │
└──── null ────────────┘
│
▼
Both match. Neither is subtype of other.
→ COMPILE ERROR: ambiguous
Resolution order:
1. Exact match (no boxing, no varargs)
2. Boxing/unboxing allowed
3. Varargs allowed
4. Most specific type wins
5. Tie → ambiguous → compile error
Key Takeaway
When overloaded methods accept Object and String, calling with null invokes the String version because String is more specific than Object. Java always picks the most specific matching method during overload resolution.
If two overloads have types that are neither a subtype of the other (like String and Integer), calling with null is ambiguous and does not compile. The fix is to cast: process((String) null).
This follows the JLS most specific method rule. In practice, avoid designing APIs where null could match multiple overloads. If it happens, use different method names.
One sentence to remember: Java picks the most specific overload – process(null) calls process(String) over process(Object) because String is a subtype of Object.
---