OOP Basics ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-08-08

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 (protectedpublic), 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 replace printName() — 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() and String 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.name you read the field declared in Toy.
  • Overriding equals without hashCode. Objects that are equal must return the same hash code, otherwise HashMap and HashSet silently 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 throws clause, the override cannot declare throws 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

Please log in or register to have a possibility to add comment.