Skip to main content

Method Overloading vs Method Overriding in Java (with Rules and Traps)

Method overloading vs overriding in Java: compile-time vs run-time polymorphism, the three-phase overload resolution order with real output, overriding rules for access, exceptions and covariant returns, @Override, and static and field hiding. Verified on JDK 25.

Method overloading means several methods in a class share a name but take different parameters; the compiler chooses one from the declared argument types, which is why it is called compile-time (static) polymorphism. Method overriding means a subclass replaces an inherited instance method with the same signature; the JVM chooses the version from the actual object at run time, which is run-time (dynamic) polymorphism.
OVERLOADINGsame name, DIFFERENT parameterssame class (or inherited)chosen by the COMPILERfrom the declared typesOVERRIDINGsame name, SAME signaturechild class replaces parent methodchosen by the JVMfrom the actual objectCompile time vs run time is the one-line answer to almost every question on this page.
The picture is the whole difference, and the rest of this page is its consequences: the order in which the compiler tries overloads, the rules an override must obey (access, exceptions, return type), and the traps where static methods and fields behave differently. Every program here was compiled and run on JDK 25, including the exact compiler errors.
Versions. Every program on this page was compiled and run on JDK 25.0.4.1 (Temurin, LTS, build 25.0.4.1+1-LTS). Where Java 8 behaved differently the text says so. The compiler errors and console output are quoted verbatim from those runs; the “Download” links at the end of each section point to the exact .java files.

Overloading vs overriding: the comparison table

FeatureMethod overloadingMethod overriding
MeaningSame name, different parameter listSame name and signature, redefined in a subclass
WhereSame class (or a class and its subclass)Subclass and superclass
ResolvedAt compile time, from declared argument typesAt run time, from the actual object
Also calledStatic or compile-time polymorphismDynamic or run-time polymorphism
Parameter listMust differMust be the same
Return typeMay differ (but return type alone is not enough)Same, or a covariant (narrower) type
Access modifierAnyCannot be weaker than the parent’s
Checked exceptionsAnyCannot be broader than the parent’s
static, final, private methodsCan be overloadedCannot be overridden (static is hidden)
@OverrideNot applicableRecommended: the compiler checks you really override
The rows to remember are Resolved and Parameter list; everything else follows from them. The next two sections show each one behaving, starting with overloading because its trap is the one interviewers love.

Overloading: the order in which the compiler picks a method

When several overloads could accept a call, the compiler tries three phases in a fixed order and stops at the first phase that finds a match: (1) exact match or widening, without boxing; (2) boxing or unboxing; (3) varargs. Within a phase it picks the most specific method.
Phase 1: exact match, primitive widening (int to long to double), subtyping. No boxing.Phase 2: boxing / unboxing (int to Integer)Phase 3: varargs (int…)
Each box is only tried if the one above found nothing, which is why an int argument goes to a long parameter before it ever considers Integer. Here are the overloads, all in OverloadResolution.java:
    static void show(long x)      { System.out.println("show(long)    " + x); }
    static void show(Integer x)   { System.out.println("show(Integer) " + x); }
    static void show(Object x)    { System.out.println("show(Object)  " + x); }
    static void show(int... x)    { System.out.println("show(int...)  " + x.length + " args"); }

    static void box(Object x)     { System.out.println("box(Object)   " + x); }
    static void box(Integer x)    { System.out.println("box(Integer)  " + x); }
    static void box(int... x)     { System.out.println("box(int...)   " + x.length + " args"); }

    static void pick(double x)    { System.out.println("pick(double)"); }
    static void pick(Object x)    { System.out.println("pick(Object)"); }
$ java OverloadResolution.java
-- phase 1: exact or widening (no boxing) --
show(long)    5
show(long)    1
show(long)    65
-- a reference argument matches by subtyping --
show(Integer) 7
-- phase 2: boxing (int becomes Integer) --
box(Integer)  5
-- phase 3: varargs, last resort --
show(int...)  0 args
show(int...)  3 args
box(int...)   0 args
-- widening beats boxing: int goes to pick(double), not pick(Object) --
pick(double)
-- most specific wins --
nul(String)
nul(String)
nul(Object)
Read it top to bottom against the three phases. show(int), show(byte) and show(char) all printed show(long): widening in phase 1 beat the Integer overload that boxing would have chosen. A genuine Integer argument matches show(Integer) by subtyping, still phase 1. Real boxing needs overloads where no primitive can widen, so box(5) picks box(Integer) in phase 2. Calling with no arguments, or with several, reaches the varargs overload in phase 3 only because nothing else fits. Note that show(Object) was never chosen at all, and that pick(i) chose the primitive double overload over Object because widening beats boxing.
The null trap. With overloads nul(Object) and nul(String), the call nul(null) picks nul(String) because String is the more specific type, as the output above shows. If the two overloads were nul(String) and nul(Integer), the same call would be ambiguous and fail to compile. Casting the argument, nul((Object) "text"), selects the Object overload explicitly.

Overloads are chosen from the declared type, not the runtime type

This is the mistake that makes people think overloading is dynamic. Two describe overloads, one taking Animal and one Dog, called with the same object through two differently typed variables:
    static void describe(Animal a) { System.out.println("describe(Animal)"); }
    static void describe(Dog d)    { System.out.println("describe(Dog)"); }
Source: OverloadResolution.java.
$ java OverloadResolution.java
-- overload chosen by compile-time type, not runtime --
describe(Animal)
describe(Dog)
The object is a Dog both times, but the first call used a variable declared as Animal, so the compiler locked in describe(Animal) before the program ran. Only the cast changed the declared type. If you ever need the runtime type to choose, that is overriding (or a switch pattern), not overloading.

Two compile errors specific to overloading

$ javac BrokenAmbiguous.java
BrokenAmbiguous.java:5: error: method g(int) is already defined in class BrokenAmbiguous
    static long g(int a) { return a; }
                ^
BrokenAmbiguous.java:6: error: reference to f is ambiguous
    public static void main(String[] args) { f(1, 2); }
                                             ^
  both method f(Integer,int) in BrokenAmbiguous and method f(int,Integer) in BrokenAmbiguous match
2 errors
Source: BrokenAmbiguous.java. Two methods that differ only by return type are the same method as far as overloading is concerned, so g is rejected as a duplicate. And f(Integer, int) versus f(int, Integer) both need one boxing step for f(1, 2), so neither is more specific and the call is ambiguous.

Overriding: the runtime type decides

An override replaces an inherited instance method. The calling code is written against the parent type, and the child’s version runs. This is the same mechanism as the polymorphism section of OOPs Concepts in Java. The classes used here:
class Animal {
    String name = "animal-field";
    String sound() { return "..."; }
    Animal self() { return this; }
    static String kind() { return "Animal.kind"; }
}
class Dog extends Animal {
    String name = "dog-field";
    @Override String sound() { return "Woof"; }
    @Override Dog self() { return this; }
    static String kind() { return "Dog.kind"; }
}
Source: OverloadResolution.java. Dog overrides sound() and also overrides self() with a narrower return type, which Java allows (a covariant return).
$ java OverloadResolution.java
-- overriding: runtime type decides --
Woof
Dog
Woof came from Dog.sound() through an Animal variable. The second line shows the covariant return: a.self() was declared to return Animal but the object returned is a Dog, and inside Dog the method is allowed to promise Dog.

What an override is not allowed to do

RuleWhy
Access cannot be weaker (public to package-private is illegal)Code written against the parent must still be able to call it
Cannot throw broader checked exceptionsCallers only handle what the parent declared
Return type must be the same or a subtypeCallers rely on the parent’s return type
Cannot override final methodsfinal exists to forbid it
Cannot override static methods (they are hidden)Static methods belong to the class, not the object
private methods are not inherited, so a same-named method is unrelatedNothing to override
Five of these rules, broken on purpose in one file, all rejected with their own message:
$ javac BrokenOverrides.java
BrokenOverrides.java:10: error: open() in Child cannot override open() in Base
    void open() { }                               // weaker access
         ^
  attempting to assign weaker access privileges; was public
BrokenOverrides.java:11: error: method does not override or implement a method from a supertype
    @Override void clos() { }                     // typo caught by @Override
    ^
BrokenOverrides.java:12: error: read() in Child cannot override read() in Base
    void read() throws IOException { }            // broader checked exception
         ^
  overridden method does not throw IOException
BrokenOverrides.java:13: error: util() in Child cannot override util() in Base
    void util() { }                               // instance cannot hide static
         ^
  overridden method is static
BrokenOverrides.java:14: error: locked() in Child cannot override locked() in Base
    void locked() { }                             // final
         ^
  overridden method is final
5 errors
Source: BrokenOverrides.java. The @Override on the typo clos() is what turned a silent bug (a new, unrelated method) into an error, which is the whole case for always writing @Override.

Hiding: static methods and fields use the declared type

Overriding is dynamic only for instance methods. A static method with the same signature in the subclass hides the parent’s, and so does a field with the same name; in both cases the declared type of the reference decides:
$ java OverloadResolution.java
-- static hiding and field hiding: reference type decides --
Animal.kind / animal-field / dog-field
Through an Animal reference, kind() ran Animal.kind and the field read animal-field, even though the object was a Dog; reading the same field through a Dog reference gave dog-field. Contrast that with sound(), which returned the Dog version through the same Animal variable. This is why fields should be private and accessed by methods (Access Modifiers in Java), and static methods should be called on the class name.
Overriding a method is not overriding a field. Nothing in Java makes a field dynamic. If a subclass needs different data, override a getter, not the field. The same rule is why the equals/hashCode contract in Top 40 Java Collections Interview Questions is expressed as overridden methods, never as fields.
Download the code. OverloadResolution.java · BrokenOverrides.java · BrokenAmbiguous.java.

Run a runnable one with java File.java (JDK 11 or later; JDK 25 recommended). Files starting with Broken are meant to fail to compile.

Common interview questions

1. What is the difference between method overloading and method overriding?

Overloading is several methods with the same name and different parameters, chosen at compile time. Overriding is a subclass redefining an inherited instance method with the same signature, chosen at run time.

2. Which is compile-time polymorphism and which is run-time polymorphism?

Overloading is compile-time (static) polymorphism; overriding is run-time (dynamic) polymorphism.

3. Can you overload a method by changing only the return type?

No. The parameter list must differ; two methods that differ only by return type are a duplicate-method compile error (shown above).

4. Can you override a static method?

No. Declaring a static method with the same signature in a subclass hides the parent method, and the declared type of the reference chooses which one runs.

5. Can you override a private or final method?

No. Private methods are not inherited and final methods cannot be overridden (compile error shown above).

6. What is a covariant return type?

An overriding method may return a subtype of the parent method’s return type. In the example, Dog.self() returns Dog where Animal.self() returns Animal.

7. Can an overriding method throw a broader exception?

Not a broader checked exception. It may throw fewer or narrower checked exceptions and any unchecked exception.

8. Can an overriding method reduce visibility?

No. It may keep or widen access (package-private to public is fine, public to package-private is an error).

9. What is the order of overload resolution?

Exact match or widening and subtyping first, then boxing or unboxing, then varargs. Within a phase the most specific method wins.

10. Which overload is called for f(null) when overloads take Object and String?

The String one, because it is more specific. With String and Integer overloads the call is ambiguous and does not compile.

11. Is the overload chosen by the declared type or the runtime type?

The declared (compile-time) type. Only overridden instance methods are dispatched on the runtime type.

12. What does @Override do?

It asks the compiler to verify the method really overrides or implements a supertype method, turning typos and signature mismatches into compile errors.

13. Can constructors be overloaded or overridden?

Overloaded yes, overridden no. See the constructors article.

Common mistakes

  • Expecting the runtime type to choose an overload. It never does; the declared type does.
  • Leaving out @Override. A typo or a changed parent signature silently creates a new method.
  • Overriding a field. Fields are hidden, not overridden; use a method.
  • Calling a static method on an instance. It compiles, it is resolved from the declared type, and it misleads readers.
  • Overloads that differ only by boxing or varargs. They produce surprising or ambiguous calls; use different names.
  • Widening access in a subclass by accident, narrowing it on purpose. You can widen; you can never narrow.

FAQ

What is the difference between method overloading and method overriding in Java?

Overloading means the same method name with different parameter lists, resolved at compile time from the declared argument types. Overriding means a subclass redefines an inherited instance method with the same signature, resolved at run time from the actual object.

Is method overloading compile-time or run-time polymorphism?

Compile-time. The compiler chooses the overload from the declared types of the arguments before the program runs.

Can we override a static method in Java?

No. A static method in a subclass with the same signature hides the parent method rather than overriding it, and the reference type decides which one is called.

What are the rules of method overriding in Java?

Same name and parameters, same or covariant return type, access not weaker, no broader checked exceptions, the parent method cannot be final, static or private. Use @Override so the compiler checks.

How does Java choose between overloaded methods?

In three phases: exact match or widening, then boxing and unboxing, then varargs, choosing the most specific method within the first phase that has a match.

Where to go next

Other articles in this Java fundamentals series:

No Comments yet!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.