Method Overriding
Method overriding lets a subclass provide its own implementation of a method that is already defined in its parent class. When you call that method on a subclass object, Java runs the subclass version — even if the variable holding the object is typed as the parent. That runtime decision is the heart of runtime polymorphism.
Why Override a Method?
Inheritance gives you a starting point, but the parent’s behavior is rarely a perfect fit for every child. Overriding lets you keep the same method signature while tailoring the logic to the specific type.
Classic example: a Shape parent class has a draw() method, but a Circle and a Rectangle each need to draw themselves differently. You override draw() in each subclass instead of adding brand-new, differently named methods.
Basic Syntax
class Animal {
void sound() {
System.out.println("Some generic animal sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Woof!");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("Meow!");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // reference type: Animal, object type: Dog
a.sound();
a = new Cat();
a.sound();
}
}
Output:
Woof!
Meow!
The variable a is of type Animal, but Java checks the actual object at runtime and calls the correct sound() method. That is dynamic dispatch.
The @Override Annotation
The @Override annotation is optional but strongly recommended. It tells the compiler: “I intend to override a method from the parent.” If you accidentally misspell the method name or use the wrong parameter types, the compiler immediately reports an error instead of silently creating a new, unrelated method.
class Dog extends Animal {
@Override
void sond() { // typo — compiler error: "method does not override"
System.out.println("Woof!");
}
}
Tip: Always use
@Override. It costs nothing and catches bugs instantly.
Rules for Method Overriding
For a valid override, all of these must hold:
| Rule | Detail |
|---|---|
| Same method name | Exactly the same name as the parent method |
| Same parameter list | Same number, types, and order of parameters |
| Same or covariant return type | Can return a subtype of the parent’s return type (covariant return) |
| Access modifier | Cannot be more restrictive than the parent (can be equal or wider) |
| Exception | Cannot declare new or broader checked exceptions than the parent |
Not static | Static methods are hidden, not overridden (see below) |
Not final | final methods cannot be overridden |
Not private | Private methods are invisible to subclasses; they cannot be overridden |
Access Modifier Example
class Parent {
protected void display() {
System.out.println("Parent");
}
}
class Child extends Parent {
@Override
public void display() { // widening protected → public is allowed
System.out.println("Child");
}
}
Narrowing the access (e.g., changing public to protected) causes a compile error.
Calling the Parent’s Version with super
Sometimes you want to extend, not replace, the parent behavior. Use the super keyword to invoke the parent’s method from inside the overriding method.
class Vehicle {
void start() {
System.out.println("Starting engine...");
}
}
class ElectricCar extends Vehicle {
@Override
void start() {
super.start(); // runs Vehicle.start() first
System.out.println("Activating battery management system.");
}
}
public class Main {
public static void main(String[] args) {
new ElectricCar().start();
}
}
Output:
Starting engine...
Activating battery management system.
Note:
super.method()can only be called from an instance method — not from astaticcontext.
Overriding vs. Hiding (Static Methods)
Static methods belong to the class, not to an instance, so they cannot be overridden in the true polymorphic sense. Declaring a static method with the same signature in a subclass hides the parent’s method — the resolved method depends on the reference type, not the object type.
class Parent {
static void greet() { System.out.println("Hello from Parent"); }
}
class Child extends Parent {
static void greet() { System.out.println("Hello from Child"); }
}
public class Main {
public static void main(String[] args) {
Parent p = new Child();
p.greet(); // prints "Hello from Parent" — resolved at compile time
}
}
Output:
Hello from Parent
This is method hiding, not overriding. There is no runtime dispatch for static calls.
Overriding and Exception Handling
An overriding method:
- May throw fewer or narrower checked exceptions than the parent.
- May not throw new or broader checked exceptions.
- May throw any unchecked (runtime) exceptions freely.
class Base {
void read() throws IOException { }
}
class Derived extends Base {
@Override
void read() throws FileNotFoundException { } // OK — narrower checked exception
// void read() throws Exception { } // COMPILE ERROR — broader checked exception
}
Overriding and the final Keyword
Mark a method final in the parent class to prevent any subclass from overriding it. This is useful for security-sensitive or invariant logic.
class BankAccount {
final double calculateInterest(double principal) {
return principal * 0.05;
}
}
class SavingsAccount extends BankAccount {
// @Override <-- compile error: cannot override final method
// double calculateInterest(double principal) { ... }
}
Overriding Abstract Methods
When you extend an abstract class or implement an interface, you are required to override all abstract methods. Overriding here is not optional — it is the contract you must fulfill.
abstract class Shape {
abstract double area(); // must be overridden in concrete subclass
}
class Circle extends Shape {
double radius;
Circle(double radius) { this.radius = radius; }
@Override
double area() {
return Math.PI * radius * radius;
}
}
public class Main {
public static void main(String[] args) {
Shape s = new Circle(5);
System.out.printf("Area: %.2f%n", s.area());
}
}
Output:
Area: 78.54
Under the Hood: vtable & Dynamic Dispatch
When the JVM loads a class, it builds a virtual method table (vtable) — an array of method pointers for every overridable method. Each class gets its own vtable. If a subclass overrides a method, the subclass vtable entry points to the new implementation; otherwise it points to the parent’s.
At a call site like a.sound():
- The JVM reads the object’s actual type from the object header.
- It looks up the vtable for that type.
- It jumps to the correct method pointer.
This lookup adds a tiny indirection cost compared to a direct call, but the JIT compiler inlines or devirtualizes most such calls once it gathers enough profiling data — so the runtime overhead is usually zero in hot code.
Note:
private,static, andfinalmethods bypass the vtable entirely; they use static dispatch (resolved at compile time), which is marginally faster and easier for the JIT to optimize.
For a deep dive, see vtable & Dynamic Dispatch.
Overloading vs. Overriding at a Glance
| Feature | Overloading | Overriding |
|---|---|---|
| Location | Same class | Parent → subclass |
| Method name | Same | Same |
| Parameters | Must differ | Must be identical |
| Return type | Can differ freely | Same or covariant |
| Resolved by | Compiler (static) | JVM at runtime (dynamic) |
| Polymorphism type | Compile-time | Runtime |
@Override needed | No | Strongly recommended |
For a side-by-side comparison with code examples, see Overloading vs Overriding.
Related Topics
- Runtime Polymorphism — how the JVM picks the right method at runtime using dynamic dispatch
- Method Overloading — the compile-time counterpart to overriding, using different parameter lists
- Inheritance — overriding only makes sense in an inheritance hierarchy
- super Keyword — invoke the parent’s overridden method from within a subclass
- Abstract Class — abstract methods require overriding in every concrete subclass
- vtable & Dynamic Dispatch — the JVM mechanism that makes runtime polymorphism work