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
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}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}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}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.
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
| Rule | Description | Example |
|---|---|---|
| Same method name | Must match exactly — case-sensitive | makeSound() overrides makeSound() |
| Same parameter list | Same types, count, and order | (String s) cannot override (int i) |
| Compatible return type | Same type or a subtype (covariant) | Dog can override returning Animal with returning Dog |
| Same or wider access | Cannot reduce visibility | public cannot be overridden as protected |
| No new checked exceptions | Can throw fewer or unchecked; not new checked | Cannot add throws IOException if parent does not declare it |
Cannot override final | final methods are locked | Compile error if attempted |
Cannot override static | Static methods are hidden, not overridden | static method uses method hiding, not polymorphism |
Cannot override private | Private methods are invisible to child | Child declares a new method, not an override |
Must use @Override | Not enforced by compiler — enforced by teams | Best 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.
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}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}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}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}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.
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}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}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.
| Aspect | Method Overriding | Method Hiding |
|---|---|---|
| Applies to | Instance methods | Static methods |
| Resolved at | Runtime — actual object type | Compile time — reference type |
| Polymorphic | Yes — JVM dispatches to correct override | No — reference type always wins |
@Override | Works correctly | Can be written but misleading |
| Keyword | None extra | static on both parent and child |
| Behaviour through parent ref | Child's version runs | Parent's version runs |
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
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}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}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}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}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
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
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
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
| Topic | Link |
|---|---|
| How method overloading differs from overriding — compile-time vs runtime | Overloading vs Overriding → |
| How polymorphism uses method overriding for runtime dispatch | Java Polymorphism → |
| How inheritance provides the class hierarchy that overriding builds on | Java Inheritance → |
| How abstract methods force every subclass to provide an override | Java Abstract Classes → |
| How the super keyword calls the parent version of an overridden method | Java super Keyword → |