LearnFrenzy
Sign in Take a free test

Inheritance and Polymorphism · Lesson 24 of 42

Method Overriding and Polymorphism

5 min read Updated Nov 7, 2026 Saurabh Samir

Polymorphism – "many forms" – means one piece of code can work with objects of many different classes, and each object responds in its own way. A payment service calls pay() without caring whether it holds a UPI, card or wallet object. This is what makes large Java applications (and frameworks like Spring) flexible.

In this lesson

  • Overriding rules and @Override
  • Dynamic dispatch: how Java picks the method at run time
  • Upcasting, downcasting and instanceof
  • Overloading vs overriding

Polymorphism in action

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Payment> payments = List.of(new UpiPayment("asha@okbank"), new CardPayment("4111"), new WalletPayment());
        for (Payment p : payments) {
            System.out.println(p.pay(499));       // one call, many behaviours
        }
    }
}

class Payment {
    String pay(double amount) { return "Paid ₹" + amount; }
}

class UpiPayment extends Payment {
    private final String upiId;
    UpiPayment(String upiId) { this.upiId = upiId; }
    @Override String pay(double amount) { return "UPI ₹" + amount + " from " + upiId; }
}

class CardPayment extends Payment {
    private final String last4;
    CardPayment(String last4) { this.last4 = last4; }
    @Override String pay(double amount) { return "Card ****" + last4 + " charged ₹" + amount; }
}

class WalletPayment extends Payment { }      // uses the inherited version

Output

UPI ₹499.0 from asha@okbank
Card ****4111 charged ₹499.0
Paid ₹499.0

Dynamic dispatch: p.pay(499)

  1. Compile timePayment has pay(double) – the call is allowed
  2. Run timelook at the real object: UpiPayment?
  3. Search its class firstUpiPayment.pay found → run it
  4. Not overridden?go up to the parent (WalletPayment → Payment.pay)

The variable's type (Payment) decides which methods you may call; the object's actual class decides which version runs.

Overriding rules

  • Same name and same parameter list as the parent method.
  • The return type must be the same or a subtype (a covariant return).
  • Access can't be more restrictive (a public method can't become protected).
  • It can't throw new or broader checked exceptions.
  • static, private and final methods can't be overridden.

Common mistake: overloading when you meant to override

Writing public boolean equals(Student other) in a class overloads equals instead of overriding equals(Object), so collections never call it. Always add @Override – the compiler then reports an error if the signature doesn't match a parent method.

Upcasting and downcasting

public class Main {
    public static void main(String[] args) {
        Animal a = new Dog();          // upcasting: automatic and safe
        a.speak();
        // a.fetch();                  // error: Animal has no fetch()

        if (a instanceof Dog d) {      // check + cast in one step (Java 16+)
            d.fetch();
        }

        Animal c = new Cat();
        try {
            Dog wrong = (Dog) c;       // downcast to the wrong type
        } catch (ClassCastException e) {
            System.out.println("ClassCastException: a Cat is not a Dog");
        }
    }
}

class Animal { void speak() { System.out.println("..."); } }
class Dog extends Animal {
    @Override void speak() { System.out.println("Woof"); }
    void fetch() { System.out.println("Fetching the ball"); }
}
class Cat extends Animal { @Override void speak() { System.out.println("Meow"); } }

Output

Woof
Fetching the ball
ClassCastException: a Cat is not a Dog

Frequent downcasting is a design smell: if you keep asking "what type is this?", the behaviour probably belongs in an overridden method instead.

Fields are not polymorphic

public class Main {
    public static void main(String[] args) {
        Parent p = new Child();
        System.out.println(p.name);        // field: chosen by variable type
        System.out.println(p.getName());   // method: chosen by object type
        p.hello();                         // static: chosen by variable type
    }
}

class Parent {
    String name = "parent";
    String getName() { return name; }
    static void hello() { System.out.println("static Parent.hello"); }
}

class Child extends Parent {
    String name = "child";             // hides, does not override
    @Override String getName() { return name; }
    static void hello() { System.out.println("static Child.hello"); }
}

Output

parent
child
static Parent.hello

A classic tricky-output question: only instance methods are dispatched at run time. Fields and static methods are resolved by the declared type.

Overloading vs overriding

OverloadingOverriding
WhereSame class (or subclass)Subclass
ParametersMust differMust be the same
DecidedCompile time (static polymorphism)Run time (dynamic polymorphism)
static methodsCan be overloadedCannot be overridden (only hidden)

Practice

Try it yourself

  1. Create shapes (Circle, Square) that override area(), and sum the areas of a mixed array.

    Show solution
    public class Main {
        public static void main(String[] args) {
            Shape[] shapes = {new Circle(1), new Square(3), new Circle(2)};
            double total = 0;
            for (Shape s : shapes) total += s.area();
            System.out.printf("%.2f%n", total);
        }
    }
    
    class Shape { double area() { return 0; } }
    class Circle extends Shape {
        final double r;
        Circle(double r) { this.r = r; }
        @Override double area() { return Math.PI * r * r; }
    }
    class Square extends Shape {
        final double side;
        Square(double side) { this.side = side; }
        @Override double area() { return side * side; }
    }

    Output

    24.71

Quick check

In Animal a = new Dog(); a.speak(); whose speak() runs?

Dog's – the object's actual class decides at run time.

Can you override a static method?

No. A static method with the same signature in a subclass hides the parent's; calls are resolved by the declared type.

Why use @Override?

The compiler checks that the method really overrides a parent method, catching signature mistakes.

Summary

  • Overriding replaces a parent method; the object's real class chooses the version at run time.
  • Upcasting is automatic; downcast only after an instanceof check (pattern matching makes it one step).
  • Fields and static methods are not polymorphic; overloading is compile-time, overriding is run-time.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Popular