Java Tutorial
🔍

Java Method Overriding

Java Method Overriding

Method overriding is when a child class provides its own implementation of a method that already exists in the parent class. The method name, parameter list, and return type must match — but the body is replaced with the child's version. At runtime, the JVM looks at the actual type of the object and calls the most specific implementation it finds.

This is the engine of runtime polymorphism. Without overriding, every object of every subtype would behave identically to the parent — the point of having a hierarchy in the first place disappears. Overriding is what gives each subclass its own identity.

What Is Method Overriding?

Method overriding happens when a subclass declares a method with the same name, same parameter list, and a compatible return type as a method in its parent class. The child's version replaces the parent's version for any object of the child type, regardless of what reference type is used to call it.

Three things must be true for a method to override rather than simply exist alongside the parent's:

  • The method name must be identical
  • The parameter list must be identical — same types, same count, same order
  • The return type must be the same or a subtype (covariant return)

If any of these differ, the method is not an override — it is an overload or a new method entirely.

Basic Method Overriding Example

Java
1// File: Animal.java 2 3public class Animal { 4 5 private final String name; 6 7 public Animal(String name) { 8 this.name = name; 9 } 10 11 public String getName() { return name; } 12 13 // Parent version — used when no override exists 14 public String makeSound() { 15 return name + " makes a generic animal sound."; 16 } 17 18 public String describe() { 19 return "Animal: " + name + " | Sound: " + makeSound(); 20 } 21}
Java
1// File: Dog.java 2 3public class Dog extends Animal { 4 5 private final String breed; 6 7 public Dog(String name, String breed) { 8 super(name); 9 this.breed = breed; 10 } 11 12 public String getBreed() { return breed; } 13 14 // Overrides Animal.makeSound() — replaces the parent's body 15 @Override 16 public String makeSound() { 17 return getName() + " barks: Woof! Woof!"; 18 } 19}
Java
1// File: Cat.java 2 3public class Cat extends Animal { 4 5 private final boolean isIndoor; 6 7 public Cat(String name, boolean isIndoor) { 8 super(name); 9 this.isIndoor = isIndoor; 10 } 11 12 @Override 13 public String makeSound() { 14 return getName() + " meows: Meow~"; 15 } 16}
Java
1// File: OverridingBasicDemo.java 2 3import java.util.List; 4 5public class OverridingBasicDemo { 6 7 public static void main(String[] args) { 8 9 List<Animal> animals = List.of( 10 new Animal("Generic Animal"), 11 new Dog("Bruno", "Labrador"), 12 new Cat("Whiskers", true) 13 ); 14 15 for (Animal animal : animals) { 16 // JVM dispatches to the correct override at runtime 17 System.out.println(animal.describe()); 18 } 19 } 20}
Output:
Animal: Generic Animal | Sound: Generic Animal makes a generic animal sound.
Animal: Bruno | Sound: Bruno barks: Woof! Woof!
Animal: Whiskers | Sound: Whiskers meows: Meow~

The loop calls animal.describe() — which is a concrete method in Animal. Inside describe(), it calls this.makeSound(). At runtime, this refers to the actual object — Dog or Cat — so the JVM dispatches to the correct makeSound() override. The parent's describe() method is reused by all subclasses; only makeSound() varies.

The @Override Annotation

@Override is not required for overriding to work — but it is required in professional Java code. It tells the compiler to verify that this method genuinely overrides something in a parent class or interface. If it does not — because of a typo in the name or a wrong parameter type — the compiler reports an error rather than silently creating a new unrelated method.

Java
1public class WrongOverrideDemo extends Animal { 2 3 public WrongOverrideDemo() { super("Test"); } 4 5 // Without @Override — this creates a NEW method, not an override 6 // makeSound is misspelled — no error reported, override silently fails 7 public String makeSounds() { // wrong name — s at the end 8 return "This never gets called polymorphically"; 9 } 10 11 // With @Override — compiler catches the mistake immediately 12 // @Override 13 // public String makeSounds() { ... } 14 // Compile error: method does not override or implement a method from a supertype 15}

Every production Java codebase enforces @Override on every overriding method. It is the fastest way to catch the most common overriding mistake — a method that was supposed to override but does not because of a typo or wrong signature.

Method Overriding Rules

RuleDescriptionExample
Same method nameMust match exactly — case-sensitivemakeSound() overrides makeSound()
Same parameter listSame types, count, and order(String s) cannot override (int i)
Compatible return typeSame type or a subtype (covariant)Dog can override returning Animal with returning Dog
Same or wider accessCannot reduce visibilitypublic cannot be overridden as protected
No new checked exceptionsCan throw fewer or unchecked; not new checkedCannot add throws IOException if parent does not declare it
Cannot override finalfinal methods are lockedCompile error if attempted
Cannot override staticStatic methods are hidden, not overriddenstatic method uses method hiding, not polymorphism
Cannot override privatePrivate methods are invisible to childChild declares a new method, not an override
Must use @OverrideNot enforced by compiler — enforced by teamsBest practice: always use it

Covariant Return Types

Java allows an overriding method to return a subtype of the parent's return type. This is called a covariant return type and makes overrides more specific without violating the contract.

Java
1// File: VehicleFactory.java 2 3public class VehicleFactory { 4 5 // Parent returns the general type 6 public Vehicle createVehicle() { 7 return new Vehicle("DEFAULT", "Standard"); 8 } 9}
Java
1// File: ElectricVehicleFactory.java 2 3public class ElectricVehicleFactory extends VehicleFactory { 4 5 // Override returns a more specific subtype — covariant return 6 @Override 7 public ElectricVehicle createVehicle() { 8 return new ElectricVehicle("EV-001", "Electric", 75); 9 } 10}
Java
1// File: Vehicle.java 2 3public class Vehicle { 4 5 private final String vehicleId; 6 private final String type; 7 8 public Vehicle(String vehicleId, String type) { 9 this.vehicleId = vehicleId; 10 this.type = type; 11 } 12 13 public String getVehicleId() { return vehicleId; } 14 public String getType() { return type; } 15 16 public String getSummary() { 17 return vehicleId + " | Type: " + type; 18 } 19}
Java
1// File: ElectricVehicle.java 2 3public class ElectricVehicle extends Vehicle { 4 5 private final int batteryKwh; 6 7 public ElectricVehicle(String vehicleId, String type, int batteryKwh) { 8 super(vehicleId, type); 9 this.batteryKwh = batteryKwh; 10 } 11 12 public int getBatteryKwh() { return batteryKwh; } 13 14 @Override 15 public String getSummary() { 16 return super.getSummary() + " | Battery: " + batteryKwh + " kWh"; 17 } 18}
Java
1// File: CovariantReturnDemo.java 2 3public class CovariantReturnDemo { 4 5 public static void main(String[] args) { 6 7 VehicleFactory standardFactory = new VehicleFactory(); 8 ElectricVehicleFactory electricFactory = new ElectricVehicleFactory(); 9 10 Vehicle standard = standardFactory.createVehicle(); 11 // ElectricVehicle is returned — no cast needed from ElectricVehicleFactory 12 ElectricVehicle electric = electricFactory.createVehicle(); 13 14 System.out.println(standard.getSummary()); 15 System.out.println(electric.getSummary()); 16 System.out.println("Battery: " + electric.getBatteryKwh() + " kWh"); 17 } 18}
Output:
DEFAULT | Type: Standard
EV-001 | Type: Electric | Battery: 75 kWh
Battery: 75 kWh

ElectricVehicleFactory.createVehicle() returns ElectricVehicle — a subtype of Vehicle. This is valid because ElectricVehicle is-a Vehicle. The caller of ElectricVehicleFactory gets back the specific type directly, with no casting needed. This pattern is common in factory methods and builder hierarchies.

super.method() in Overriding — Extending vs Replacing

An override can either completely replace the parent behaviour or extend it using super.method(). The choice depends on whether the parent's logic is still needed.

Java
1// File: BaseLogger.java 2 3public class BaseLogger { 4 5 protected String prefix; 6 7 public BaseLogger(String prefix) { 8 this.prefix = prefix; 9 } 10 11 public void log(String message) { 12 System.out.println("[" + prefix + "] " + message); 13 } 14}
Java
1// File: TimestampLogger.java 2 3import java.time.LocalTime; 4import java.time.format.DateTimeFormatter; 5 6public class TimestampLogger extends BaseLogger { 7 8 public TimestampLogger(String prefix) { 9 super(prefix); 10 } 11 12 // Extends parent behaviour — adds timestamp without duplicating base logic 13 @Override 14 public void log(String message) { 15 String time = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")); 16 super.log("[" + time + "] " + message); // reuses parent's format 17 } 18}
Java
1// File: SuperInOverrideDemo.java 2 3public class SuperInOverrideDemo { 4 5 public static void main(String[] args) { 6 7 BaseLogger base = new BaseLogger("APP"); 8 TimestampLogger timed = new TimestampLogger("APP"); 9 10 base.log("Server started"); 11 timed.log("Server started"); 12 } 13}
Output:
[APP] Server started
[APP] [10:30:45] Server started

TimestampLogger.log() calls super.log() to reuse the parent's formatting and adds the timestamp on top. Without super.log(), it would need to duplicate the bracket-and-prefix formatting — and any future change to the parent format would not propagate to the child.

Method Overriding vs Method Hiding

Static methods cannot be overridden — they are hidden. This is a critical difference that directly affects polymorphic behaviour.

AspectMethod OverridingMethod Hiding
Applies toInstance methodsStatic methods
Resolved atRuntime — actual object typeCompile time — reference type
PolymorphicYes — JVM dispatches to correct overrideNo — reference type always wins
@OverrideWorks correctlyCan be written but misleading
KeywordNone extrastatic on both parent and child
Behaviour through parent refChild's version runsParent's version runs
Java
1// File: OverridingVsHidingDemo.java 2 3public class OverridingVsHidingDemo { 4 5 static class Parent { 6 public static String staticMethod() { return "Parent static"; } 7 public String instanceMethod() { return "Parent instance"; } 8 } 9 10 static class Child extends Parent { 11 // Hiding — NOT overriding (static) 12 public static String staticMethod() { return "Child static"; } 13 14 // Overriding — true polymorphism (instance) 15 @Override 16 public String instanceMethod() { return "Child instance"; } 17 } 18 19 public static void main(String[] args) { 20 21 Parent ref = new Child(); // reference type: Parent, actual type: Child 22 23 // Static — resolved at compile time by reference type 24 System.out.println("Static via Parent ref : " + Parent.staticMethod()); 25 System.out.println("Static via Child ref : " + Child.staticMethod()); 26 27 // Instance — resolved at runtime by actual object type 28 System.out.println("Instance via Parent ref: " + ref.instanceMethod()); 29 } 30}
Output:
Static via Parent ref : Parent static
Static via Child ref  : Child static
Instance via Parent ref: Child instance

The static method is resolved by the reference type — Parent.staticMethod() always returns "Parent static" regardless of the actual object. The instance method is resolved by the actual object type — ref.instanceMethod() returns "Child instance" even though ref is declared as Parent. This is the polymorphism difference.

Real-World Example — Payroll Calculation System

The Business Problem

You are building the payroll engine for a staffing company — similar to what Infosys or Wipro uses for its HR systems. Full-time employees get basic salary plus allowances minus deductions. Contract employees get a daily rate times days worked. Both are Employee objects. The payroll runner processes all employees through one loop without knowing the concrete type — overriding handles the rest.

Implementation

Java
1// File: PayrollConfig.java 2 3public final class PayrollConfig { 4 5 public static final double DA_RATE = 0.10; 6 public static final double HRA_RATE = 0.15; 7 public static final double PF_RATE = 0.12; 8 9 private PayrollConfig() {} 10}
Java
1// File: Employee.java 2 3public abstract class Employee { 4 5 private final String employeeId; 6 private final String fullName; 7 private final String department; 8 9 protected Employee(String employeeId, String fullName, String department) { 10 this.employeeId = employeeId; 11 this.fullName = fullName; 12 this.department = department; 13 } 14 15 public String getEmployeeId() { return employeeId; } 16 public String getFullName() { return fullName; } 17 public String getDepartment() { return department; } 18 19 // Abstract — each subclass calculates pay differently 20 public abstract double calculateMonthlyPay(); 21 22 // Concrete — shared payslip format; calls overridden calculateMonthlyPay() 23 public String generatePaySlip() { 24 return String.format("%-10s | %-22s | %-18s | Pay: Rs.%,.2f", 25 employeeId, fullName, department, calculateMonthlyPay()); 26 } 27}
Java
1// File: FullTimeEmployee.java 2 3public class FullTimeEmployee extends Employee { 4 5 private final double basicSalary; 6 7 public FullTimeEmployee(String employeeId, String fullName, 8 String department, double basicSalary) { 9 super(employeeId, fullName, department); 10 this.basicSalary = basicSalary; 11 } 12 13 @Override 14 public double calculateMonthlyPay() { 15 double da = basicSalary * PayrollConfig.DA_RATE; 16 double hra = basicSalary * PayrollConfig.HRA_RATE; 17 double pf = basicSalary * PayrollConfig.PF_RATE; 18 return basicSalary + da + hra - pf; 19 } 20}
Java
1// File: ContractEmployee.java 2 3public class ContractEmployee extends Employee { 4 5 private final double dailyRate; 6 private final int daysWorked; 7 8 public ContractEmployee(String employeeId, String fullName, 9 String department, double dailyRate, int daysWorked) { 10 super(employeeId, fullName, department); 11 this.dailyRate = dailyRate; 12 this.daysWorked = daysWorked; 13 } 14 15 @Override 16 public double calculateMonthlyPay() { 17 return dailyRate * daysWorked; // no allowances or deductions for contract 18 } 19}
Java
1// File: PayrollDemo.java 2 3import java.util.List; 4 5public class PayrollDemo { 6 7 public static void main(String[] args) { 8 9 List<Employee> payroll = List.of( 10 new FullTimeEmployee("FT-001", "Ananya Krishnan", "Backend Eng", 85000.0), 11 new FullTimeEmployee("FT-002", "Rohan Desai", "DevOps", 72000.0), 12 new ContractEmployee("CT-001", "Priya Mehta", "QA", 3500.0, 22), 13 new ContractEmployee("CT-002", "Karan Singh", "Design",4000.0, 18) 14 ); 15 16 System.out.println("=== Monthly Payroll ===\n"); 17 double totalPayroll = 0; 18 for (Employee emp : payroll) { 19 System.out.println(emp.generatePaySlip()); 20 totalPayroll += emp.calculateMonthlyPay(); 21 } 22 System.out.printf("%nTotal payroll: Rs.%,.2f%n", totalPayroll); 23 } 24}
Output:
=== Monthly Payroll ===

FT-001     | Ananya Krishnan        | Backend Eng       | Pay: Rs.96,050.00
FT-002     | Rohan Desai            | DevOps            | Pay: Rs.81,360.00
CT-001     | Priya Mehta            | QA                | Pay: Rs.77,000.00
CT-002     | Karan Singh            | Design            | Pay: Rs.72,000.00

Total payroll: Rs.3,26,410.00

The payroll loop calls emp.generatePaySlip() on every Employee. generatePaySlip() calls calculateMonthlyPay() internally — which dispatches to FullTimeEmployee.calculateMonthlyPay() or ContractEmployee.calculateMonthlyPay() at runtime. No instanceof check, no if-else by type. Just one loop, one method call, correct result for each type.

Best Practices

Always annotate overrides with @Override

Every professional Java team enforces this. Without @Override, a typo or signature mismatch silently creates a new method instead of overriding. The annotation costs nothing and catches mistakes before they reach runtime.

Use super.method() when extending, not when replacing

If the child's override adds to the parent's behaviour, call super.method() and append the extra logic. If the override completely replaces the parent's behaviour, do not call super.method() — omitting it is the signal that the parent's code is no longer relevant.

Keep overriding methods focused on their own responsibility

An overriding method should do what the parent declared — not more. When an override starts adding unrelated side effects — sending emails, updating databases, modifying shared state — it violates the principle of least surprise. Callers of the parent type do not expect these side effects.

Never call overridable methods from constructors

When a constructor calls an overridable method, Java dispatches to the child's override even before the child constructor's body has run. The child's fields are at their default values. This produces NullPointerException or incorrect results that are extremely difficult to diagnose.

Common Mistakes

Mistake 1 — Wrong Parameter Type Causes Overload Instead of Override

Java
1public class Notifier { 2 public void send(String message) { 3 System.out.println("Notifier: " + message); 4 } 5} 6 7public class SmsNotifier extends Notifier { 8 9 // Intended to override — but parameter type is Object, not String 10 // This is an OVERLOAD, not an override 11 @Override // Compile error — correctly caught by @Override 12 public void send(Object message) { 13 System.out.println("SMS: " + message); 14 } 15}

Without @Override, this compiles silently. send(String) on an SmsNotifier reference would call Notifier.send(String) — never reaching the child's method. @Override catches this immediately.

Mistake 2 — Reducing Access Modifier in Override

Java
1public class Service { 2 public String getStatus() { return "Running"; } 3} 4 5public class MockService extends Service { 6 7 // Compile error — cannot reduce access from public to protected 8 @Override 9 protected String getStatus() { return "Mock Running"; } // error 10}
Compile error: getStatus() in MockService cannot override getStatus() in Service;
attempting to assign weaker access privileges

An overriding method cannot be less visible than the parent. The parent declared public — the override must also be public. Any caller who holds a Service reference expects to call getStatus() publicly — the override cannot break that expectation.

Mistake 3 — Declaring a New Checked Exception in an Override

Java
1import java.io.IOException; 2 3public class DataReader { 4 public String readData() { return "data"; } 5} 6 7public class FileDataReader extends DataReader { 8 9 // Compile error — cannot add new checked exception not in parent 10 @Override 11 public String readData() throws IOException { // error 12 return "file data"; 13 } 14}
Compile error: readData() in FileDataReader cannot override readData() in DataReader;
overridden method does not throw IOException

A caller holding a DataReader reference does not know to handle IOException — it was not declared in the parent. Adding it would break all callers. The fix is to declare it in the parent, use RuntimeException (unchecked), or wrap the checked exception.

Interview Questions

Q1. What is method overriding in Java?

Method overriding is when a child class declares a method with the same name, same parameter list, and a compatible return type as a method in its parent class. At runtime, the JVM calls the child's version when the actual object type is the child — regardless of the reference type used. It is the mechanism behind runtime polymorphism and dynamic dispatch. @Override annotation makes the intent explicit and lets the compiler verify the override is correct.

Q2. What is the difference between method overriding and method hiding in Java?

Method overriding applies to instance methods — the JVM resolves which implementation to call at runtime based on the actual object type. This is polymorphic. Method hiding applies to static methods — when a child declares a static method with the same signature as a parent's static method, the parent's version is hidden but not replaced. The version called is determined by the reference type at compile time, not the actual object type at runtime. Static methods are not polymorphic.

Q3. What are the rules for method overriding in Java?

The overriding method must have the same name and parameter list as the parent. The return type must be the same or a subtype (covariant return). The access modifier must be the same or wider — cannot reduce visibility. No new checked exceptions may be declared that are not in the parent's throws clause. The method cannot be private, static, or final in the parent — all three prevent overriding. Using @Override is not required by the compiler but is mandatory in professional code.

Q4. What is a covariant return type in method overriding?

A covariant return type allows an overriding method to return a subtype of the parent method's return type. If the parent method returns Animal, the child can override it to return Dog — since Dog is-a Animal. This makes factory methods and builder hierarchies more precise because callers of the child class get back the specific type without casting.

Q5. Why should overridable methods not be called from constructors?

When a constructor calls an instance method that is overridden in a child class, Java dispatches to the child's override — even before the child's constructor body has run. The child's fields are still at their default values: null, 0, or false. The overriding method then operates on uninitialised state, producing incorrect results that are extremely difficult to debug. Only private or final methods — which cannot be overridden — are safe to call from constructors.

Q6. Can you override a private method in Java?

No. Private methods are not visible to subclasses — they are not inherited. A child class can declare a method with the same name and signature as a parent's private method, but it is a completely new, unrelated method — not an override. Calling the method through a parent-type reference will always invoke the parent's private version; the child's version is never called polymorphically.

FAQs

What is method overriding in Java?

Method overriding is when a subclass provides its own implementation of a method declared in the parent class. The signature must match exactly. At runtime, the JVM calls the child's version based on the actual object type — enabling runtime polymorphism.

Is @Override mandatory in Java?

The compiler does not require it. But every professional Java team treats it as mandatory because it tells the compiler to verify the override is correct. Without it, a typo in the method name or a wrong parameter type silently creates a new method instead of overriding, causing the polymorphism to silently fail.

Can you override a static method in Java?

No. Static methods cannot be overridden — they can only be hidden. A child can declare a static method with the same signature, but it is not polymorphic. The version called depends on the reference type at compile time, not the actual object type at runtime.

What is the difference between overriding and overloading?

Overriding redefines an inherited method in a child class — same name, same parameters, resolved at runtime. Overloading defines multiple methods with the same name but different parameters in the same or a child class — resolved at compile time. Overriding is runtime polymorphism; overloading is compile-time polymorphism.

Can you override a final method in Java?

No. A method declared final in the parent cannot be overridden in any subclass. Attempting to do so is a compile-time error. final on a method guarantees that the parent's implementation is the one that always runs, regardless of the actual object type.

What happens to the parent method when a child overrides it?

The parent method still exists in the parent class. It is accessible through super.method() from within the child's overriding method. Objects of the parent type still call the parent's version. Only objects of the child type (or further subclasses) use the child's override. The parent method is not deleted — it is replaced in the method dispatch chain for the child type only.

Summary

Method overriding is the direct implementation of runtime polymorphism — one method name, one call, different behaviour depending on the actual object type at runtime. The JVM resolves overrides through dynamic dispatch at runtime, not compile-time. The rules — same name, same parameters, compatible return type, same or wider access, no new checked exceptions — are what the compiler checks when @Override is present.

The most practically important rule is @Override on every override. It turns a silent runtime bug (wrong method name, wrong parameter type) into an immediate compile error. The second most important is understanding the difference between overriding (instance methods, polymorphic) and hiding (static methods, not polymorphic) — this distinction is tested in virtually every Java interview.

What to Read Next

TopicLink
How method overloading differs from overriding — compile-time vs runtimeOverloading vs Overriding →
How polymorphism uses method overriding for runtime dispatchJava Polymorphism →
How inheritance provides the class hierarchy that overriding builds onJava Inheritance →
How abstract methods force every subclass to provide an overrideJava Abstract Classes →
How the super keyword calls the parent version of an overridden methodJava super Keyword →
Java Method Overriding | DevStackFlow