Method Overriding
Method overriding is an object-oriented programming feature in Java where a child class provides its own implementation of a method that is already defined in its parent class. It is one of the most important practical uses of inheritance because it allows a subclass to specialize behavior while still preserving the parent-child relationship. The parent class defines a general method, and the child class redefines that same method to perform behavior that is more suitable for the child object.
The most important idea behind method overriding is runtime polymorphism. When an overridden method is called
through a parent reference, Java does not simply execute the method based on the reference type. Instead, it
checks the actual object created at runtime and executes the method belonging to that object. This is why a
variable of type Animal can hold a Dog object, and when sound() is
called, Java can execute the Dog version of the method. This behavior makes Java programs more
flexible and extensible.
Method overriding is a very high-frequency Java interview topic because it connects inheritance,
polymorphism, dynamic method dispatch, access modifiers, exception rules, covariant return types, the
@Override annotation, and the difference between overriding and overloading. A strong answer is
not limited to saying that the child class changes the parent method. It should also explain the rules that
make a method truly overridden and the runtime behavior that makes overriding useful.
What Is Method Overriding?
Method overriding occurs when a subclass defines a method with the same method signature as a method in its superclass. The method name must be the same, and the parameter list must be the same. The return type must either be the same or a covariant return type, which means a more specific subtype of the original return type. The relationship must involve inheritance; without a parent-child relationship, there is no overriding.
The purpose of overriding is to allow the child class to provide behavior that fits its own meaning. A parent
class may define a general operation, but each child class may perform that operation differently. For
example, an Animal class may define a sound() method. A Dog class can
override it to bark, a Cat class can override it to meow, and a Bird class can
override it to chirp. The method name remains the same because the operation is conceptually the same, but
the implementation differs because each object behaves differently.
This is different from simply creating a new method in the child class. In overriding, the child method replaces the inherited behavior for that specific child object. If external code calls the method on a child object, the child implementation is used. If the child object is stored in a parent reference, the child implementation is still used for instance methods. This is the core of runtime polymorphism.
class Parent {
void show() {
System.out.println("Parent show");
}
}
class Child extends Parent {
void show() {
System.out.println("Child show");
}
}
In this example, Child overrides the show() method of Parent. The
method has the same name and parameter list. When the method is called on a Child object, the
child implementation is executed. The parent method still exists, but the child class provides the behavior
that applies to child objects.
Why Method Overriding Is Needed
Method overriding is needed because inheritance without specialization would be limited. If every child class were forced to use only the parent implementation, child classes could reuse code but could not express their own behavior properly. Overriding solves this by allowing common structure to remain in the parent class while child classes provide their own details.
Overriding also enables flexible design. Code can be written against a parent type, and different child
objects can respond in their own ways. This reduces conditional logic. Instead of writing a long chain of
if or switch statements to check whether an object is a dog, cat, or bird, the
program can call the same method and let Java dispatch the correct implementation at runtime. This makes the
code easier to extend because adding a new child class does not require rewriting every caller.
In real applications, overriding is common in frameworks, domain models, service classes, UI components, and testing libraries. A base class may define a template method, and subclasses override parts of the behavior. A framework may call a method at the right time, while the application class provides the specific implementation. This is one reason overriding is central to object-oriented programming in Java.
Basic Example
The classic example uses an Animal superclass and a Dog subclass. The parent class
defines a general sound() method. The child class overrides that method to provide a dog-specific
sound. The important line is Animal a = new Dog();. The reference type is
Animal, but the actual object is Dog. When a.sound() is executed,
Java calls the Dog version because overridden instance methods are resolved using the runtime
object type.
class Animal {
void sound() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
Animal a = new Dog();
a.sound(); // Dog barks
This example shows why overriding matters. The caller only needs to know that the object is some kind of
Animal. The caller does not need to know the exact subclass to get correct behavior. Java makes
that decision at runtime. This allows code to be open for new child classes while keeping existing caller
code stable.
Rules of Method Overriding (Very Important)
Method overriding has strict rules because Java must be able to guarantee that a child method is truly replacing a parent method. If the method signature changes, the child method is not overriding; it is overloading or defining a separate method. If the access modifier becomes more restrictive, existing code that worked with the parent type may break. If exception rules are violated, callers of the parent method may be forced to handle exceptions they were not promised. These rules protect type safety.
1. Method Signature Must Be Same
void display()
The method name and parameter list must match exactly. The parameter list includes the number, order, and types of parameters. If a child method changes the parameter type or adds another parameter, it becomes overloading, not overriding. This is one of the most common beginner mistakes.
2. Access Modifier Cannot Be More Restrictive
class A {
protected void show() { }
}
class B extends A {
public void show() { } // allowed
}
An overriding method can keep the same visibility or make visibility broader, but it cannot make visibility
narrower. For example, a protected parent method can be overridden as protected or
public, but not as private. This rule preserves substitutability. Code that is
allowed to call the parent method should not suddenly lose access when working with a child object.
3. Return Type Must Be Same or Covariant
class A {
A getObject() { }
}
class B extends A {
B getObject() { } // covariant return type
}
Java allows covariant return types in overriding. This means the child method may return a subclass of the
return type declared by the parent method. If the parent method returns A, a child method can
return B when B is a subclass of A. This gives child classes a way to
be more specific while still satisfying the parent method contract.
4. Static Methods Cannot Be Overridden
static void test() { }
Static methods belong to the class, not to an object. Because overriding is based on runtime object type, static methods do not participate in overriding. If a child class declares a static method with the same signature as a static method in the parent class, the method is hidden, not overridden. Method hiding is resolved using the reference type or class name, not the runtime object type.
5. final Methods Cannot Be Overridden
final void display() { }
A final method cannot be overridden. The final keyword tells Java that the method
implementation must remain unchanged in subclasses. This is useful when the parent class depends on a method
behaving exactly as written, or when a method is part of a stable design contract that subclasses should not
alter.
6. Constructors Cannot Be Overridden
Constructors cannot be overridden because constructors are not inherited. A constructor belongs to the class
in which it is declared and is responsible for initializing that exact class. A child constructor can call a
parent constructor using super(), but it cannot override the parent constructor. Constructor
chaining and method overriding are separate concepts.
7. Exception Rules
Exception rules are another important part of overriding. An overriding method cannot throw broader checked exceptions than the parent method declares. It can throw the same checked exception, a narrower checked exception, unchecked exceptions, or no exception. This rule prevents a child method from surprising callers with new checked exceptions that the parent contract did not require them to handle.
@Override Annotation (Best Practice)
The @Override annotation is not required, but it is a best practice. It tells the compiler that
the method is intended to override a parent method. If the method signature does not actually match a parent
method, the compiler reports an error. This protects developers from silent mistakes such as misspelling a
method name or using a slightly different parameter list.
@Override
void show() { }
In professional Java code, @Override also improves readability. It clearly communicates that the
method is not just a new method in the child class; it is intentionally replacing or specializing inherited
behavior. This is helpful during code reviews and maintenance because it makes inheritance relationships
visible at the method level.
Method Overriding vs Method Overloading
Method overriding and method overloading are often confused because both involve methods with the same name. The difference is where and how they happen. Overloading usually occurs in the same class, although it can also occur across inherited methods, and it requires a different parameter list. Overriding occurs between a parent class and a child class, and it requires the same parameter list. Overloading is resolved at compile time. Overriding is resolved at runtime.
| Feature | Overriding | Overloading |
|---|---|---|
| Occurs In | Inheritance | Same class |
| Parameters | Same | Different |
| Return Type | Same / covariant | Any |
| Binding | Runtime | Compile-time |
| Polymorphism | Runtime | Compile-time |
A simple way to remember the distinction is this: overloading gives multiple ways to call a method, while overriding gives a child-specific implementation of a parent method. Overloading is about method convenience. Overriding is about polymorphic behavior.
Runtime Polymorphism Explained
Runtime polymorphism is the behavior that makes overriding powerful. A parent reference can point to a child object. When an overridden instance method is called through that reference, Java uses the actual runtime object type to decide which method implementation to execute. This process is called dynamic method dispatch.
Parent p = new Child();
p.show();
In this statement, the reference type is Parent, but the object type is Child. If
show() is overridden in Child, the child version runs. The compiler checks whether
the method exists in the reference type, but the JVM chooses the implementation based on the actual object.
This is why a parent reference can be used for flexible designs while still preserving child-specific
behavior.
Runtime polymorphism applies to instance methods, not fields and not static methods. Fields are resolved based on reference type. Static methods are hidden and resolved based on reference type or class name. This distinction is a common interview trap because the syntax can look similar while the behavior is different.
Using super in Overriding
When a child class overrides a method, it does not always need to discard the parent implementation. Often,
the parent method contains useful common logic. The child can call the parent version using
super.methodName() and then add child-specific behavior. This is a clean way to extend behavior
without duplicating parent code.
class Child extends Parent {
void show() {
super.show();
System.out.println("Child logic");
}
}
This pattern is common in real projects. A parent validation method may check common rules, and a child
validation method may call super.validate() before checking additional rules. A parent logging
method may perform standard logging, while the child adds extra context. The key is that the child should
call super only when the parent behavior is still meaningful for the child.
Design Value of Method Overriding
Method overriding supports a design style where the stable part of a system depends on abstractions and the
variable part is supplied by subclasses. Suppose an application has a general Payment class with
a process() method. A CardPayment, WalletPayment, and
BankTransferPayment class can each override process(). The caller can work with a
Payment reference and does not need to know every payment type in advance. Adding a new payment
type becomes easier because the new subclass provides its own overridden behavior.
This design reduces repeated condition checking. Without overriding, the caller might need to inspect the payment type and decide which logic to execute. With overriding, each object carries its own behavior. This is more aligned with object-oriented design because behavior lives with the object that owns it.
Good overriding also improves maintainability. Parent classes define common contracts, and child classes provide specialized implementations. When the contract is stable and the subclass responsibilities are clear, the system can grow without large changes to existing code. However, overriding should not be used to create surprising behavior. A child method should still honor the meaning promised by the parent method.
Overriding and the Parent Contract
Every overridden method should respect the parent method's contract. A contract means the expectation that callers have when they call the method. If a parent method promises to calculate a price, the child method should also calculate a price. It may calculate it differently, but it should not perform an unrelated action such as deleting data or sending a notification as its main responsibility. Overriding should specialize the behavior, not change the meaning of the behavior.
This principle matters because of polymorphism. Code that uses a parent reference expects parent-like behavior. If a child class overrides a method in a way that violates the parent expectation, the program may compile successfully but behave incorrectly. This is why object-oriented design often says that subclasses should be substitutable for their parent classes. In practical terms, a child object should be safe to use anywhere the parent type is expected.
For example, if a parent Account class defines a withdraw() method, a child class
may override it to apply account-specific rules. A savings account may enforce a minimum balance, and a
current account may allow overdraft. But the method should still represent withdrawal behavior. If the
overridden method performs something unrelated, the hierarchy becomes misleading and difficult to maintain.
How Overriding Works in Real Projects
In real Java projects, method overriding is rarely used as an isolated syntax exercise. It is usually part of a larger design where a parent type defines a common operation and multiple child classes provide different implementations. This pattern appears in business applications, automation frameworks, web applications, and library code. The caller depends on a general type, while the object itself decides the exact behavior.
Consider a reporting application. A parent class named Report may define a method called
generate(). Child classes such as PdfReport, ExcelReport, and
HtmlReport can override generate() because each report type generates output in a
different format. The code that triggers report generation can work with a Report reference and
call generate() without knowing the exact report type. This keeps the calling code simple and
makes the system easier to extend when a new report format is added.
The same idea appears in test automation frameworks. A base test class may define setup and cleanup methods, while child test classes override specific hooks to provide module-specific preparation. A framework may call the same method name for every test, but each child class can provide behavior appropriate for its own scenario. This is one reason overriding is not only an academic OOP concept; it is a practical mechanism used by frameworks to allow customization.
Method Overriding and Dynamic Dispatch
Dynamic dispatch is the internal principle behind runtime polymorphism. At compile time, Java checks whether a method call is valid based on the reference type. At runtime, if the method is overridden, Java dispatches the call to the implementation that belongs to the actual object. This two-step behavior explains many interview questions. The compiler asks, "Is this method available on the reference type?" The JVM later asks, "Which implementation should run for this actual object?"
This distinction is important when a parent reference points to a child object. The parent reference limits what methods can be called directly. If the child class has a brand-new method that does not exist in the parent class, the parent reference cannot call it without casting. However, if the child class overrides a method that exists in the parent class, the parent reference can call the method, and the child implementation will run. This is why overriding works well when child classes share a common parent contract.
Dynamic dispatch also helps reduce fragile branching logic. Instead of checking object types manually, code can rely on the object to provide the correct behavior. This makes adding new subclasses less disruptive. The new subclass overrides the required methods, and existing code that works with the parent type can often continue unchanged.
Overriding and Abstract Classes
Method overriding becomes even more important when abstract classes are involved. An abstract class can declare an abstract method without providing implementation. Any concrete child class must override that method and provide the missing behavior. This allows the parent class to define a required operation while leaving the details to subclasses.
For example, an abstract Shape class may declare an abstract method called
calculateArea(). A Circle, Rectangle, and Triangle must
each override that method because each shape calculates area differently. The parent class defines the
common expectation that every shape can calculate area. The child classes provide the correct formulas.
This design is useful because it separates common capability from specific implementation. Code that works
with Shape objects can call calculateArea() confidently, even though the exact
calculation depends on the runtime object. This is a clean example of polymorphism supported by method
overriding.
Common Design Mistakes with Overriding
One common design mistake is overriding a method only to completely change its meaning. This may satisfy the
compiler, but it creates confusing behavior. A child method should remain consistent with the parent method's
purpose. If the parent method is named save(), the child override should still save something.
If the child needs a completely unrelated operation, it should usually define a new method instead of
misusing overriding.
Another mistake is creating a parent class too early and forcing child classes into an inheritance hierarchy before the common behavior is clear. Overriding is most useful when there is a stable contract shared by related classes. If the hierarchy is artificial, subclasses may override too many methods just to avoid inherited behavior. That is a sign that inheritance may not be the right design.
A third mistake is forgetting the @Override annotation. Without the annotation, a small
signature difference can silently create an overloaded method instead of an overridden method. The code may
compile, but runtime behavior will not match expectations. Using @Override turns this silent
logic error into an immediate compile-time error.
Interview Explanation Strategy
In interviews, method overriding should be explained in layers. First, define it clearly: a subclass provides its own implementation of a method already defined in the superclass. Second, mention the rules: same method signature, same or covariant return type, no reduction in access level, valid exception handling, no overriding of constructors, static methods, or final methods. Third, connect it to runtime polymorphism by explaining that Java chooses the method implementation based on the actual object at runtime.
A strong answer should also compare overriding with overloading. Overriding requires inheritance and runtime
binding. Overloading requires a different parameter list and compile-time binding. If you include a small
example such as Animal a = new Dog(); a.sound();, the explanation becomes practical and easy to
remember.
Finally, mention why overriding is useful in real design. It supports extensibility, reduces conditional logic, and allows parent references to work with child behavior. This shows that you understand the concept beyond syntax and can apply it in real Java development.
Method Overriding Examples and Rules
1. Basic Method Overriding
class A {
void show() {
System.out.println("Parent");
}
}
class B extends A {
void show() {
System.out.println("Child");
}
public static void main(String[] args) {
B b = new B();
b.show();
}
}
Explanation: Child provides its own implementation.
Output: Child
2. Runtime Polymorphism (Upcasting)
class A {
void show() {
System.out.println("A");
}
}
class B extends A {
void show() {
System.out.println("B");
}
public static void main(String[] args) {
A a = new B();
a.show();
}
}
Explanation: Method resolved at runtime.
Output: B
3. Overriding with super Call
class A {
void show() {
System.out.println("Parent");
}
}
class B extends A {
void show() {
super.show();
System.out.println("Child");
}
public static void main(String[] args) {
new B().show();
}
}
Explanation: Parent logic reused.
Output:
Parent
Child
4. Method Signature Must Match
class A {
void test(int x) {}
}
class B extends A {
// void test(String s) {} // Overloading, not overriding
}
Explanation: Same method name + same parameter list required.
5. Cannot Reduce Access Level
class A {
protected void show() {}
}
class B extends A {
// void show() {} // Cannot reduce visibility
}
Explanation: Visibility must be same or broader.
6. Increasing Access Level (Allowed)
class A {
protected void show() {
System.out.println("A");
}
}
class B extends A {
public void show() {
System.out.println("B");
}
public static void main(String[] args) {
new B().show();
}
}
Explanation: protected to public is allowed.
Output: B
7. Overriding with Covariant Return Type
class A {
Object get() {
return "Java";
}
}
class B extends A {
String get() {
return "Selenium";
}
public static void main(String[] args) {
A a = new B();
System.out.println(a.get());
}
}
Explanation: Return type can be subclass.
Output: Selenium
8. Overriding and Checked Exceptions
import java.io.IOException;
class A {
void read() throws IOException {}
}
class B extends A {
void read() throws IOException {} // same exception
}
Explanation: Same or subclass exception allowed.
9. Cannot Throw Broader Checked Exception
import java.io.IOException;
class A {
void read() throws IOException {}
}
class B extends A {
// void read() throws Exception {} // Not allowed
}
Explanation: Cannot widen checked exception.
10. Overriding Without Exception
class A {
void show() throws Exception {}
}
class B extends A {
void show() {} // allowed
}
Explanation: Child can remove exception.
11. final Method Cannot Be Overridden
class A {
final void show() {}
}
class B extends A {
// void show() {} // Compile-time error
}
Explanation: final prevents overriding.
12. static Methods Are NOT Overridden (Method Hiding)
class A {
static void show() {
System.out.println("A");
}
}
class B extends A {
static void show() {
System.out.println("B");
}
public static void main(String[] args) {
A.show();
B.show();
}
}
Explanation: Static methods are hidden.
Output:
A
B
13. Instance vs Static (Interview Trap)
class A {
void show() {}
}
class B extends A {
// static void show() {} // Cannot override instance with static
}
Explanation: Instance and static method mismatch is not allowed.
14. Variable Hiding vs Method Overriding
class A {
int x = 10;
void show() {
System.out.println(x);
}
}
class B extends A {
int x = 20;
void show() {
System.out.println(x);
}
public static void main(String[] args) {
A a = new B();
a.show();
System.out.println(a.x);
}
}
Explanation: Methods are resolved at runtime, while variables are resolved at compile time.
Output:
20
10
15. Overriding in Multilevel Inheritance
class A {
void show() {
System.out.println("A");
}
}
class B extends A {
void show() {
System.out.println("B");
}
}
class C extends B {
void show() {
System.out.println("C");
}
public static void main(String[] args) {
A a = new C();
a.show();
}
}
Explanation: Lowest child implementation executed.
Output: C
16. Using @Override Annotation
class A {
void show() {}
}
class B extends A {
@Override
void show() {}
}
Explanation: Helps catch signature mistakes at compile time.
17. Missing Override (Common Bug)
class A {
void show(int x) {}
}
class B extends A {
void show() {} // Not overriding
}
Explanation: Different parameters mean the method is not overridden.
18. Overriding and instanceof
class A {
void show() {
System.out.println("A");
}
}
class B extends A {
void show() {
System.out.println("B");
}
public static void main(String[] args) {
A a = new B();
if (a instanceof B) {
a.show();
}
}
}
Explanation: Runtime type check.
Output: B
19. Overriding with Abstract Method
abstract class A {
abstract void show();
}
class B extends A {
void show() {
System.out.println("Implemented");
}
public static void main(String[] args) {
A a = new B();
a.show();
}
}
Explanation: Mandatory implementation.
Output: Implemented
20. Interview Summary – Method Overriding
class A {
void show() {
System.out.println("A");
}
}
class B extends A {
void show() {
System.out.println("B");
}
public static void main(String[] args) {
A obj = new B();
obj.show();
}
}
Explanation: IS-A relationship, runtime binding, core of polymorphism.
Output: B
Common Beginner Mistakes
- Changing parameter list (becomes overloading)
- Forgetting inheritance
- Overriding static methods
- Using more restrictive access modifier
- Not using @Override
Interview-Ready Answers
Short Answer
Method overriding allows a subclass to provide a specific implementation of a method already defined in its parent class.
Detailed Answer
In Java, method overriding occurs when a subclass defines a method with the same signature and return type as a parent class method. The method call is resolved at runtime based on the object type, enabling runtime polymorphism.
Key Takeaway
Method overriding enables dynamic behavior in Java. It is the core mechanism behind runtime polymorphism, making Java applications flexible and extensible.