3

Here's a very basic program I wrote to test how Java overloading is resolved when an exact match is not found, and what priority is assigned to other matching methods.

import java.io.*;
public class Main
{
 
 
 //NOTE: Comparable<Integer> ties with Serializable and with Number, however Number wins over Serializable
 
 // static void test(Number x) {
 // System.out.println("here in Number x");
 // }
 
 
 // static void test(Serializable x) {
 // System.out.println("here in Serializable x");
 // }
 
 // static void test(Comparable<Integer> x) {
 // System.out.println("here in Comparable<Integer> x");
 // }
 
public static void main(String[] args) {
 test(10);
 }
}

When test(Number x) is commented out: test(Comparable\<Integer\> x) gives an ambiguous reference error when it's present alongside test(Serializable x).

When test(Serializable x) is commented out The same happens for test(Comparable\<Integer\> x) and test(Number x).

So I assumed that all three of these must be interpreted as having the same hierarchy depth by the JVM. This makes sense, since Integer subclasses Number and implements Serializable and Comparable<Integer>.

However, when I comment out the Comparable<Integer> method, then Number wins over Serializable, and test(Number x) is called

To put it simply, I assume a sort of transitivity where if method A clashes with method B, and method A also clashes with method C, then method B must also clash with method C, but that's not the case.

Can someone please explain what's going on here?

For context, I used an online compiler to test the code. I mention this because I'm not sure if this behavior is JVM implementation-specific.

Edit: I did come across method overloading priority in Java, but that does not answer my question. That material explains why certain methods are prioritized over others, but I am asking: why is it that when method A ties with method B and C, method B and method C don't give the same ambiguity error? Maybe I'm missing something very basic here.

Edit 2: I removed the additional question about varargs (as suggested by the reviewer). Maybe I'll post another question for that.

asked Dec 22, 2025 at 15:25
5
  • Perhaps I'm missing something, but essentially if you have 3 available overloads (A, B, C), where A is the clear winner, and B and C are ambiguous/tied, then when you make the overloaded call there actually isn't any ambiguity: it will simply pick A. Commented Dec 22, 2025 at 15:32
  • test(Comparable<Integer> x) is ambiguous with test(Serializable x) and test(Comparable<Integer> x) is ambiguous with test(Number x) So , I guess I kinda expected a transitive nature where when A=B and A=C, then B=C, and by that logic, Serializable must give the same ambiguity error with Number, but that's not the case here. A is not the winner everytime. That's what I'm confused about. Commented Dec 22, 2025 at 15:41
  • @gambit36 Please edit your post to focus only on one situation. It looks like you are talking about Comparable<Integer> and Serializable overloads, but also on varargs int, Integer and Object overloads. It is not clear which one you are talking about or why you mention both situations. Remove any unrelated code/comments from your question. Commented Dec 22, 2025 at 16:38
  • @Progman Done. I posted that alongside since it was causing a similar contradiction that I am confused about, but anyway, I've removed that part for now. Commented Dec 22, 2025 at 17:31
  • Note that X... is really just syntactic sugar for X[] (with corresponding grammar rules) in terms of type resolution. Commented Dec 22, 2025 at 18:17

1 Answer 1

6

How does Java resolve method overloading ambiguity with parent classes and interfaces are involved?

The Java Language Specification is the authoritative reference for overload resolution and all other details of Java language semantics. In JLS 25, resolving the specific method chosen for a method invocation is one of the main topics covered in section 15.12. Overload resolution is part of determining the method signature (JLS25 15.12.2). The summary description is:

The second step searches the type determined in the previous step for member methods. This step uses the name of the method and the argument expressions to locate methods that are both accessible and applicable, that is, declarations that can be correctly invoked on the given arguments.

There may be more than one such method, in which case the most specific one is chosen. The descriptor (signature plus return type) of the most specific method is the one used at run time to perform the method dispatch.

(Italics in the original.) The second paragraph is about overloaded methods, and the last sentence is the theme that the ensuing rules for overload resolution are meant to implement.

  1. The first phase performs overload resolution without permitting boxing or unboxing conversion, or the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the second phase.

That's approximately widening -> (boxing, varargs) in the notation presented in the question. (And note that upcasting is a form of widening conversion). Among your example methods, including the commented-out ones, that rule would distinguish test(long) above all the others, but it would not distinguish among the rest.

  1. The second phase performs overload resolution while allowing boxing and unboxing, but still precludes the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the third phase.

That's approximately (boxing, boxing + widening) -> (varargs (+ anything)). It distinguishes the remaining non-variadic methods from the variadic ones, but it does not distinguish among the several members of either of those groups.

And for completeness:

  1. The third phase allows overloading to be combined with variable arity methods, boxing, and unboxing.

When application of those rules produces multiple applicable candidates (I do not cover details of applicability in this answer), an attempt is made to choose a most specific one of them. This is detailed in JLS25 15.12.2.5. The summary description is:

The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error. In cases such as an explicitly typed lambda expression argument (§15.27.1) or a variable arity invocation (§15.12.2.4), some flexibility is allowed to adapt one signature to the other.

Note well that "more specific" is a relation over method signatures alone. The actual arguments to the method invocation have all the effect they ever will on this analysis at the previous stages of identifying applicable methods and selecting among those based on whether (un)boxing conversions or variable-arity invocation would be required.

It gets pretty technical from there, but here's how it works out among your methods with argument type Integer, Number, Comparable<Integer>, Serializable, Object:

  • test(Integer) is most specific because Integer is a subclass of Number and Object and it implements Comparable<Integer> and Serializable

  • test(Number) is more specific than the Object, Serializable alternatives for the same reasons that test(Integer) is. This method is neither more nor less specific than test(Comparable<Integer>) because neither argument type is compatible with all argument types that the other is compatible with.

  • test(Comparable<Integer>) is more specific than test(Object), but this method is neither more nor less specific than test(Serializable) because neither argument type is compatible with all argument types that the other is compatible with.

  • test(Serializable) is more specific than test(Object) because the class of every object is a subclass of Object.

Among the variadic alternatives, the analysis is based on the declared parameter types of their parameters, in a variable-arity sense (so int, Integer, and Object in your case, as opposed to arrays of those). It looks at whether all the parameter types of one signature are the same as or more specific (as that applies to types) than the corresponding parameter types of the other. The details are again technical, but for your test(int...), test(Integer...), and test(Object...), it works out that

  • test(Integer...) is more specific than test(Object...), but
  • test(int...) is neither more nor less specific than either of the other two, because primitives are not comparable with reference types for specificity.
answered Dec 22, 2025 at 16:58
Sign up to request clarification or add additional context in comments.

4 Comments

Note: in some cases, most often involving candidates with different return types, there can be an additional step of choosing a preferred alternative among two or more that have substantially equivalent parameter lists. That doesn't apply to the example presented in the question, though, and I don't think it can happen without at least one of the candidates being inherited from a supertype.
Thanks for the reply! I have edited my question since then to make it clearer & focused(as recommended). But here is what I've understood so far: test(Comparable<Integer>) is neither more or less specific than test(Number), so we get an ambiguity error For same reasons, test(Comparable<Integer>) gives ambiguity error with test(Serializable) But I'm still not clear as to why test(Number) is chosen over test(Serializable) Is it because every Number is Serializable? But not every Number implements Comparable<Integer> & not every Serializable implements Comparable<Integer>?
@gambit36, your edit narrows the question to cover fewer cases. I don't think that was necessary at all here, but the change doesn't invalidate this answer, which now more than addresses the result.
You're right. It actually took me a couple re-reads to fully grasp it, but I get it now. Thanks a lot!

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.