Java Method Overriding Tutorial with Real-Time Project Example
Last Updated on: 12th Nov 2025 15:08:29 PM
Welcome to this beginner-friendly tutorial on Method Overriding in Java! Method Overriding is a runtime polymorphism feature that allows a subclass to provide a specific implementation of a method that is already defined in its superclass.
This tutorial uses a real-world project-based example — a Banking System with Multiple Account Types — to show how method overriding is used in production software.
What is Method Overriding?
Method Overriding occurs when a subclass provides a new implementation of a method that is already defined in its superclass. The method in the subclass must have:
-
Same name
-
Same parameter list
-
Same return type (or covariant return type)
It is used to change or modify the behavior of an inherited method.
👉 It represents Runtime Polymorphism (also known as Dynamic Polymorphism).
Rules for Method Overriding
-
Method must be inherited (not private, final, or static)
-
Same name, parameters, and return type
-
Subclass method cannot reduce visibility (e.g., public → private)
-
Use @Override to catch errors
-
Can throw fewer or narrower exceptions
Example of Method Overriding
class Vehicle {
void run() {
System.out.println("The vehicle is running...");
}
}
class Bike extends Vehicle {
@Override
void run() {
System.out.println("The bike is running at 60 km/h.");
}
}
class Car extends Vehicle {
@Override
void run() {
System.out.println("The car is running at 100 km/h.");
}
}
public class VehicleTest {
public static void main(String[] args) {
Vehicle v1 = new Bike(); // runtime decision
Vehicle v2 = new Car();
v1.run(); // Calls Bike’s run()
v2.run(); // Calls Car’s run()
}
}
Output
The bike is running at 60 km/h.
The car is running at 100 km/h.
How Method Overriding Works Internally
-
The reference variable of the parent class points to a child class object.
-
At runtime, the JVM decides which method to call based on the actual object type, not the reference type.
-
This behavior is known as Dynamic Method Dispatch.
Important Points to Remember
| Keyword | Description |
|---|---|
@Override |
Ensures that you’re actually overriding a parent method (compiler checked). |
super |
Can be used inside the child class to call the parent class method. |
final |
If a method is final, it cannot be overridden. |
static |
Static methods are hidden, not overridden. |
Example: Using super Keyword
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void sound() {
super.sound(); // Call parent method
System.out.println("Dog barks");
}
}
public class SuperExample {
public static void main(String[] args) {
Dog d = new Dog();
d.sound();
}
}
Output
Animal makes a sound
Dog barks
Real-Time Project: Banking System
Let’s build a Banking Application that supports:
-
Savings Account (with interest)
-
Current Account (with overdraft)
-
Fixed Deposit Account (locked funds)
We’ll use method overriding to calculate interest and withdrawal rules differently for each account type.
Step 1: Superclass – BankAccount
// BankAccount.java
public class BankAccount {
protected String accountNumber;
protected String holderName;
protected double balance;
public BankAccount(String accountNumber, String holderName, double initialBalance) {
this.accountNumber = accountNumber;
this.holderName = holderName;
this.balance = initialBalance;
}
// Common method - can be overridden
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("Deposited: ₹" + amount + " | New Balance: ₹" + balance);
} else {
System.out.println("Invalid deposit amount.");
}
}
// Method to be overridden
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
System.out.println("Withdrawn: ₹" + amount + " | Remaining: ₹" + balance);
} else {
System.out.println("Insufficient funds or invalid amount.");
}
}
// Method to be overridden
public void calculateInterest() {
System.out.println("No interest applicable for basic account.");
}
// Display account info
public void displayInfo() {
System.out.println("Account: " + accountNumber);
System.out.println("Holder: " + holderName);
System.out.println("Balance: ₹" + balance);
}
}
Step 2: Subclasses with Overridden Methods
1. SavingsAccount.java
public class SavingsAccount extends BankAccount {
private double interestRate = 4.0; // 4% per year
public SavingsAccount(String accountNumber, String holderName, double initialBalance) {
super(accountNumber, holderName, initialBalance);
}
@Override
public void withdraw(double amount) {
double minBalance = 1000;
if (amount > 0 && (balance - amount) >= minBalance) {
balance -= amount;
System.out.println("[Savings] Withdrawn: ₹" + amount + " | Balance: ₹" + balance + " (Min ₹1000 maintained)");
} else {
System.out.println("[Savings] Withdrawal failed: Minimum balance ₹1000 required.");
}
}
@Override
public void calculateInterest() {
double interest = balance * (interestRate / 100);
balance += interest;
System.out.println("[Savings] Interest added: ₹" + interest + " | New Balance: ₹" + balance);
}
}
2. CurrentAccount.java
public class CurrentAccount extends BankAccount {
private double overdraftLimit = 5000;
public CurrentAccount(String accountNumber, String holderName, double initialBalance) {
super(accountNumber, holderName, initialBalance);
}
@Override
public void withdraw(double amount) {
double maxWithdraw = balance + overdraftLimit;
if (amount > 0 && amount <= maxWithdraw) {
balance -= amount;
System.out.println("[Current] Withdrawn: ₹" + amount + " | Balance: ₹" + balance + " (Overdraft used if negative)");
} else {
System.out.println("[Current] Withdrawal exceeds overdraft limit of ₹5000.");
}
}
@Override
public void calculateInterest() {
System.out.println("[Current] No interest on current accounts.");
}
}
3. FixedDepositAccount.java
public class FixedDepositAccount extends BankAccount {
private double interestRate = 7.5;
private boolean isMatured = false;
public FixedDepositAccount(String accountNumber, String holderName, double initialBalance) {
super(accountNumber, holderName, initialBalance);
}
@Override
public void withdraw(double amount) {
if (isMatured) {
super.withdraw(amount); // Use parent's logic
} else {
System.out.println("[FD] Withdrawal not allowed: FD not matured yet!");
}
}
@Override
public void calculateInterest() {
double interest = balance * (interestRate / 100);
balance += interest;
System.out.println("[FD] High interest added: ₹" + interest + " | Total: ₹" + balance);
}
public void matureFD() {
isMatured = true;
System.out.println("[FD] Fixed Deposit matured. Withdrawal now allowed.");
}
}
Step 3: Main App – BankingSystem
// BankingSystem.java
import java.util.ArrayList;
import java.util.List;
public class BankingSystem {
public static void main(String[] args) {
// Polymorphic List
List<BankAccount> accounts = new ArrayList<>();
accounts.add(new SavingsAccount("SAV001", "Amit Sharma", 5000));
accounts.add(new CurrentAccount("CUR001", "Priya Verma", 10000));
accounts.add(new FixedDepositAccount("FD001", "Raj Patel", 50000));
System.out.println("BANKING SYSTEM - MONTHLY PROCESSING\n");
// Runtime Polymorphism in action
for (BankAccount account : accounts) {
account.displayInfo();
account.deposit(1000);
account.withdraw(2000);
account.calculateInterest();
System.out.println("-".repeat(50));
}
// Special case: Mature FD
FixedDepositAccount fd = (FixedDepositAccount) accounts.get(2);
fd.matureFD();
fd.withdraw(10000);
}
}
Output
BANKING SYSTEM - MONTHLY PROCESSING
Account: SAV001
Holder: Amit Sharma
Balance: ₹5000.0
Deposited: ₹1000.0 | New Balance: ₹6000.0
[Savings] Withdrawn: ₹2000.0 | Balance: ₹4000.0 (Min ₹1000 maintained)
[Savings] Interest added: ₹160.0 | New Balance: ₹4160.0
--------------------------------------------------
Account: CUR001
Holder: Priya Verma
Balance: ₹10000.0
Deposited: ₹1000.0 | New Balance: ₹11000.0
[Current] Withdrawn: ₹2000.0 | Balance: ₹9000.0 (Overdraft used if negative)
[Current] No interest on current accounts.
--------------------------------------------------
Account: FD001
Holder: Raj Patel
Balance: ₹50000.0
Deposited: ₹1000.0 | New Balance: ₹51000.0
[FD] Withdrawal not allowed: FD not matured yet!
[FD] High interest added: ₹3825.0 | Total: ₹54825.0
--------------------------------------------------
[FD] Fixed Deposit matured. Withdrawal now allowed.
Withdrawn: ₹10000.0 | Remaining: ₹44825.0
Why Use Method Overriding? (Real Project Benefits)
|
Feature |
Benefit |
|---|---|
|
withdraw() |
Different rules per account type |
|
calculateInterest() |
Unique logic for each account |
|
Runtime Decision |
Correct method called based on actual object |
|
Extensible |
Add StudentAccount, NRIAccount easily |
How Java Chooses the Right Method?
|
Object |
Method Called |
|---|---|
|
new SavingsAccount() |
SavingsAccount.withdraw() |
|
new CurrentAccount() |
CurrentAccount.withdraw() |
JVM decides at runtime using vtable (virtual method table).
Real-Time Use Cases
|
System |
Overridden Method |
|---|---|
|
Payment Gateway |
processPayment() in CreditCard, UPI, Wallet |
|
Notification System |
send() in Email, SMS, Push |
|
Report Generator |
generate() in PDFReport, ExcelReport |
|
Vehicle System |
startEngine() in ElectricCar, PetrolCar |
Common Mistakes
// NOT overriding
public void deposit(int amount) { } // Different parameter type
// Valid override
@Override
public void deposit(double amount) { } // Same signature
// Cannot override final method
class A { final void lock() {} }
class B extends A { void lock() {} } // ERROR
Summary
-
Method Overriding allows a subclass to modify behavior of its superclass method.
-
It enables runtime polymorphism, improving flexibility and reusability.
-
Always use the
@Overrideannotation for clarity and safety. -
It’s one of the most important OOP concepts in Java.
Project Tip:
In your Banking App, override:
-
applyCharges() for different account types
-
sendAlert() for SMS/email preferences
-
generateStatement() for PDF/HTML
You now master Method Overriding in Java with a real-world, scalable banking system!
Keep building — happy coding! ![]()