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
| Feature | Method overloading | Method overriding |
|---|---|---|
| Meaning | Same name, different parameter list | Same name and signature, redefined in a subclass |
| Where | Same class (or a class and its subclass) | Subclass and superclass |
| Resolved | At compile time, from declared argument types | At run time, from the actual object |
| Also called | Static or compile-time polymorphism | Dynamic or run-time polymorphism |
| Parameter list | Must differ | Must be the same |
| Return type | May differ (but return type alone is not enough) | Same, or a covariant (narrower) type |
| Access modifier | Any | Cannot be weaker than the parent’s |
| Checked exceptions | Any | Cannot be broader than the parent’s |
| static, final, private methods | Can be overloaded | Cannot be overridden (static is hidden) |
@Override | Not applicable | Recommended: the compiler checks you really override |
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.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 overloadsnul(Object)andnul(String), the callnul(null)picksnul(String)becauseStringis the more specific type, as the output above shows. If the two overloads werenul(String)andnul(Integer), the same call would be ambiguous and fail to compile. Casting the argument,nul((Object) "text"), selects theObjectoverload explicitly.
Overloads are chosen from the declared type, not the runtime type
This is the mistake that makes people think overloading is dynamic. Twodescribe 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
| Rule | Why |
|---|---|
| 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 exceptions | Callers only handle what the parent declared |
| Return type must be the same or a subtype | Callers rely on the parent’s return type |
Cannot override final methods | final 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 unrelated | Nothing to override |
$ 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 withjava File.java(JDK 11 or later; JDK 25 recommended). Files starting withBrokenare 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
- OOPs Concepts in Java (polymorphism in a running example)
- Constructors in Java (overloaded constructors and chaining)
- Top 40 Java Collections Interview Questions (overriding equals and hashCode)
- Top 40 Java Concurrency Interview Questions (overriding Runnable.run and Thread)
- Top 50 Java Stream API Coding Interview Questions (overloaded and functional method signatures)
- Polymorphism Example in Java (a short practice program)
No Comments yet!