Method Overloading vs Overriding in Java
A class declares two methods, doJob(Toy) and doJob(Doll). You write Toy toy = new Doll(); doJob(toy); and expect the Doll version to run, because the object really is a Doll. The console prints Toy version. Nothing is broken: those two methods are overloaded, not overridden, and overloads are picked by the compiler from the declared type of the variable. Swap in overriding and the very same call prints Doll version. That one difference is what this lesson is about.
Method overriding in Java means declaring a method in a subclass with the same name and the same parameter list as a method of its superclass, so the subclass supplies its own implementation. Method overloading means declaring several methods with the same name but different parameter lists in the same class or in a subclass. Overriding is resolved at runtime by the type of the object; overloading is resolved at compile time by the declared types of the arguments.
1. Overloading vs overriding: the short answer
Both mechanisms let one method name serve several behaviours, and that is where the confusion starts. The single criterion that separates them: overloading is decided by the compiler, overriding is decided by the JVM while the program is running.
| Criterion | Overriding | Overloading |
|---|---|---|
| Where the methods live | In different classes related by inheritance | Usually in the same class (a subclass may add overloads too) |
| Parameter list | Must be identical | Must be different |
| Return type | Same type or a subtype (covariant) | Free, but it alone cannot create an overload |
| Access modifier | May be widened, never narrowed | Any |
| Checked exceptions | Same, narrower or none | Any, including new and broader ones |
| Binding | Late (dynamic) binding, by the type of the object | Early (static) binding, by the declared type of the arguments |
| Form of polymorphism | Runtime polymorphism | Compile-time polymorphism |
@Override annotation | Applicable and recommended | Not applicable — it causes a compile error |
static / final / constructors | Cannot be overridden | Can all be overloaded |
One sentence worth memorising
For an overridden method the object type decides which implementation runs. For an overloaded method the reference type decides which method is called, and that decision is already burned into the bytecode before the program starts.
2. Method overriding
An overriding method is a method of a subclass that has the same signature — the same name and the same parameter list — as an inherited method of the superclass. It is used for two things:
- to give a subclass its own implementation of behaviour that the superclass already defined;
- to enable runtime polymorphism, where one reference type drives many implementations.
Example 2.1. Method overriding
The subclass Doll overrides printName() of the superclass Toy:
class Toy {
public void printName() {
System.out.println("Toy");
}
}
class Doll extends Toy {
@Override
public void printName() {
System.out.println("Doll");
}
}
public class TestToys {
public static void main(String[] args) {
Toy toy = new Toy();
Toy doll = new Doll();
toy.printName();
doll.printName();
}
} The output is:
Toy
Doll The variable doll is declared as Toy, yet the Doll implementation runs. The declared type controls only which methods you are allowed to call; the actual object controls which implementation executes. This lookup at runtime is called dynamic method dispatch, or late binding.
3. Rules of overriding
The compiler checks every one of these before it accepts a method as an override:
| Element | Rule | What happens if you break it |
|---|---|---|
| Method name | Must match the superclass method | It is simply a new method of the subclass |
| Parameter list | Must match exactly: types, order and count | You get an overload, not an override |
| Return type | Same type, or a subtype of it (covariant return) | Compile error |
| Access modifier | May be widened (protected → public), never narrowed | Compile error: attempting to assign weaker access privileges |
| Checked exceptions | May be dropped or narrowed, never added or broadened | Compile error |
| Unchecked exceptions | Any RuntimeException or Error is allowed | No restriction |
| Inheritance | Only inherited instance methods can be overridden | A private method is not inherited, so a same-name method is independent |
| Abstract methods | Must be implemented, unless the subclass is abstract too | Compile error |
static methods | Cannot be overridden — they are hidden | It compiles, but resolution follows the reference type |
final methods | Cannot be overridden | Compile error: cannot override final method |
| Constructors | Are not inherited, so they cannot be overridden | Same-name constructors are overloads |
4. Method overloading
Overloaded methods reuse one method name for several operations that differ in what they accept. The parameter list must differ — in the number of parameters, in their types, or in their order. Overloading is what lets System.out.println() accept an int, a String, a char[] or an arbitrary object under a single familiar name.
Example 4.1. Overloading with primitives
The method multiply is overloaded to accept different numeric types:
class Multiplier {
public int multiply(int x, int y) {
System.out.println("Multiply int");
return x * y;
}
public double multiply(double x, double y) {
System.out.println("Multiply double");
return x * y;
}
}
public class Test {
public static void main(String... args) {
Multiplier m = new Multiplier();
System.out.print(m.multiply(3, 4));
}
} The output is:
Multiply int
12 The literals 3 and 4 are of type int, so the int version is an exact match and wins. Written as m.multiply(3.0, 4.0), the same call would select the double version and print 12.0.
5. Rules of overloading
| Element | Rule |
|---|---|
| Parameter list | Must change — different types, a different number of parameters, or a different order |
| Return type | May differ, but only once the parameter list already differs. Two methods that differ only by return type do not compile |
| Access modifier | May differ freely |
| Checked exceptions | May be new or broader — there is no relationship to enforce |
| Location | Same class, or a subclass that adds a new parameter list to an inherited name |
| Constructors | Can be overloaded, and usually are |
static / final / private | All can be overloaded — the restrictions that apply to overriding do not apply here |
| Binding | The declared type of the arguments decides the target method, at compile time |
6. How the compiler picks an overloaded method
Example 6.1. Overloading with object references
This is the case from the opening paragraph. Two overloads accept a Toy and a Doll, where Doll extends Toy:
class Toy {}
class Doll extends Toy {}
public class TestToys {
public void doJob(Toy toy) {
System.out.println("Toy version");
}
public void doJob(Doll doll) {
System.out.println("Doll version");
}
public static void main(String[] args) {
TestToys testToys = new TestToys();
Doll doll = new Doll();
Toy toy1 = new Toy();
Toy toy2 = new Doll();
testToys.doJob(doll);
testToys.doJob(toy1);
testToys.doJob(toy2);
}
} The output is:
Doll version
Toy version
Toy version The third call is the interesting one. At runtime toy2 refers to a Doll, but the compiler only ever saw a variable declared as Toy, and it hard-wired the call to doJob(Toy). No later check can change that. If you need the behaviour to follow the real object, you need overriding, not overloading.
Example 6.2. Widening beats boxing, boxing beats varargs
When no overload matches exactly, the compiler makes up to three passes over the candidates: first it tries widening a primitive, then autoboxing, and only then varargs. A favourite interview question:
public class Resolver {
static void print(long value) {
System.out.println("long");
}
static void print(Integer value) {
System.out.println("Integer");
}
static void print(int... values) {
System.out.println("varargs");
}
public static void main(String[] args) {
print(5);
}
} The output is:
long The literal 5 is an int. Widening it to long is free and is tried first, so print(Integer) and print(int...) never get a chance. Remove the long overload and the same call prints Integer; remove that one too and it prints varargs.
A related question is what testToys.doJob(null) does in Example 6.1. It compiles, and it prints Doll version: null fits both overloads, so the compiler picks the most specific one, and Doll is a subtype of Toy. If the two parameter types were unrelated, the call would be rejected as ambiguous.
7. Calling the parent version with super
An override does not have to replace the parent behaviour completely. To run the superclass version and then extend it, call it through super.methodName():
class Toy {
public void printName() {
System.out.println("Toy");
}
}
class Doll extends Toy {
@Override
public void printName() {
super.printName(); // the Toy version runs first
System.out.println("Doll");
}
}
public class SuperDemo {
public static void main(String[] args) {
new Doll().printName();
}
} The output is:
Toy
Doll Inside the subclass, a plain call to printName() would recurse into the subclass version; super.printName() is the only way to reach the superclass implementation. It goes exactly one level up the hierarchy — there is no super.super in Java.
8. The @Override annotation
@Override tells the compiler that the method below is meant to override a method of a superclass or an interface. It is optional, and the code behaves the same without it, but it converts a silent bug into a compile error: if the name has a typo or the parameter list does not match, the method is quietly an overload, and nothing tells you.
The textbook case is equals. Writing equals(User obj) instead of equals(Object obj) is an overload, so collections keep using the identity-based equals inherited from Object, and the bug shows up far from the declaration:
import java.util.Objects;
public class User {
private String name;
@Override
public boolean equals(Object obj) { // with @Override a wrong signature fails to compile
if (this == obj) return true;
if (!(obj instanceof User)) return false;
return Objects.equals(name, ((User) obj).name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public String toString() {
return "User{name='" + name + "'}";
}
} Use it as a checker, not decoration
Put @Override on every method you intend to override. It cannot be used on an overload, so it is also the cheapest way to prove which of the two you actually wrote. Since Java 6 it is legal on methods that implement an interface as well; in Java 5 that was a compile error.
9. Covariant return types
Since Java 5 an overriding method may narrow the return type to any subtype of the original — this is called a covariant return type. Widening it is still forbidden.
class Box {
double width;
double height;
}
class HeavyBox extends Box {
double weight;
}
class BoxFactory {
Box getInstance() {
return new Box();
}
}
class HeavyBoxFactory extends BoxFactory {
@Override
HeavyBox getInstance() { // narrowing Box to HeavyBox is allowed
return new HeavyBox();
}
} The practical benefit is that callers working with the concrete factory get the concrete type back and do not have to cast:
HeavyBoxFactory factory = new HeavyBoxFactory();
HeavyBox box = factory.getInstance(); // no cast needed The classic example in the standard library is clone(): Object declares it as returning Object, and implementations override it to return their own type. Note that a covariant return alone never creates an overload — the parameter lists are identical, so this is still overriding.
10. Access modifiers, static and final
An override may widen visibility but never narrow it: protected may become public, package-private may become protected or public. The reverse breaks substitutability — code holding a superclass reference would suddenly lose access to a method it was promised:
class Parent {
protected void show() { }
}
class Child extends Parent {
@Override
public void show() { } // OK: access widened
}
class Bad extends Parent {
@Override
private void show() { } // compile error: attempting to assign weaker access privileges
} A final method cannot be overridden at all, and a final class cannot be subclassed, so none of its methods can be overridden either. A private method is not inherited: a subclass method with the same signature is a separate method, and marking it @Override fails to compile.
static methods deserve their own note. A subclass may declare a static method with the same signature, but that is method hiding, not overriding, and the version to run is chosen from the reference type at compile time:
class Base {
public static void go() {
System.out.println("Base.go");
}
}
class Sub extends Base {
public static void go() {
System.out.println("Sub.go");
}
}
public class HidingDemo {
public static void main(String[] args) {
Base ob = new Sub();
ob.go(); // the compiler looks at the variable type -> Base
Sub.go();
}
} The output is:
Base.go
Sub.go Never call a static method through a reference
ob.go() compiles, but every modern IDE flags it with «Static member accessed via instance reference». Call it as Base.go() or Sub.go() and the hiding stops being surprising, because the class name says out loud which version runs.
11. Where developers get tripped up
A handful of tricky cases that catch beginners and experienced developers alike:
- Changing the parameter list turns an override into an overload.
printName(String prefix)in a subclass does not replaceprintName()— both exist, and the parent version is what polymorphic code will keep calling. - Two methods that differ only by return type do not compile.
int getId()andString getId()in the same class is an error, not an overload — the return type is not part of what distinguishes overloads. - Fields are hidden, not overridden. A field with the same name in a subclass shadows the parent field, and field access is resolved from the reference type: for
Toy t = new Doll(); t.nameyou read the field declared inToy. - Overriding
equalswithouthashCode. Objects that are equal must return the same hash code, otherwiseHashMapandHashSetsilently lose entries. - Calling an overridable method from a constructor. While the superclass constructor runs, the subclass fields are still at their default values (
0,null), so the overriding method sees an unfinished object. - Trying to add a checked exception. If the superclass method declares no
throwsclause, the override cannot declarethrows IOException. Unchecked exceptions are not restricted. - Assuming overloading is polymorphism at runtime. It is compile-time (static) polymorphism. Only overriding is dispatched dynamically.
Frequently asked questions
What is the main difference between overloading and overriding in Java?
Overloading means several methods share a name inside one class but take different parameter lists, and the compiler decides which one to call from the declared types of the arguments. Overriding means a subclass redeclares an inherited method with exactly the same name and parameter list, and the JVM decides which implementation to run from the actual type of the object. Overloading is compile-time polymorphism, overriding is runtime polymorphism.
Can a method be overloaded by changing only the return type?
No. The return type is not part of what distinguishes overloaded methods, so declaring int getId() and String getId() in the same class is a compile error about a duplicate method. The parameter list has to differ first; once it does, the return types may differ as well. The JVM itself does include the return type in the method descriptor, which is why compiled bytecode can hold such pairs, but Java source code cannot.
Can static, final or private methods be overridden or overloaded?
They can all be overloaded, and none of them can be overridden. A static method with the same signature in a subclass hides the parent method, and the version that runs is chosen from the reference type at compile time. A final method cannot be overridden at all. A private method is not inherited, so a same-name method in the subclass is an independent method. Constructors cannot be overridden either, but they are very commonly overloaded.
Which overload is chosen when several of them match?
The compiler prefers the most specific applicable method and works in three passes: first it tries an exact match or widening of a primitive, then autoboxing, and only then varargs. So with print(long), print(Integer) and print(int...) available, the call print(5) prints long. When a call passes null and two overloads accept unrelated reference types, no method is most specific and the compiler reports an ambiguous call.
Is @Override required, and what does it actually do?
It is optional, and overriding works without it. What it does is make the compiler verify that the method really overrides something in a superclass or interface, so a typo in the name or a mismatched parameter list fails to compile instead of quietly becoming an overload. The classic case it catches is writing equals(User obj) instead of equals(Object obj). The annotation cannot be applied to an overloaded method, which makes it a reliable way to tell the two mechanisms apart in your own code.
Official documentation: Overriding and Hiding Methods (Oracle Java Tutorials), Defining Methods and Overloading, JLS 8.4.9. Overloading.
Comments