In the previous lesson, we explored method overriding and contrasted it with overloading. You learned that overriding is a core feature of inheritance where a subclass provides its own implementation for a method it inherits. This mechanism is not just for changing behavior; it's the key that unlocks one of the most powerful concepts in object-oriented programming: runtime polymorphism.
This lesson will connect the dots between method overriding and true polymorphic behavior. We'll investigate how Java determines which method to run at execution time, not at compile time. To do this, we will master two related concepts: upcasting and dynamic method dispatch. Understanding this will enable you to write more flexible, scalable, and maintainable code—a hallmark of a professional Java developer.
1. Upcasting: Viewing an Object Through a More General Lens
Before we can see polymorphism in action, we need to understand a prerequisite concept called upcasting. In simple terms, upcasting is the process of treating an object of a subclass as an object of its superclass. Because a subclass instance is-a superclass instance (a Dog is an Animal), this is a perfectly safe and implicit operation in Java.
The syntax for upcasting involves assigning a new subclass object to a reference variable of the superclass type.

Let's see this with a code example. The Baeldung article "Upcasting vs. Downcasting in Java" provides a clear definition and an initial example.
Upcasting vs. Downcasting in Java
This article explains the fundamentals of casting. We will focus only on the upcasting section for now.
Read the section titled "Upcasting in Java". Pay close attention to the Animal myDog = new Dog(); line. This is the upcast.
As the article demonstrates, when you upcast, the object you create is still a Dog in memory, but the "handle" you are using to interact with it (myDog) is of type Animal. This has a critical consequence: the reference type determines which methods are available to call at compile time.
Let's refine the example from the article:
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
public void fetch() {
System.out.println("Dog fetches the ball");
}
}
public class Main {
public static void main(String[] args) {
// This is upcasting
Animal myAnimal = new Dog();
// This is valid because makeSound() is defined in the Animal class.
myAnimal.makeSound();
// This would cause a COMPILE ERROR.
// myAnimal.fetch(); // The compiler says: "The method fetch() is undefined for the type Animal."
}
}
The compiler only "sees" the Animal reference type, which doesn't have a fetch() method. It prevents you from calling it. So, if upcasting restricts us, why is it so useful? It's because it enables dynamic method dispatch.
2. Dynamic Method Dispatch: How Java Achieves Runtime Polymorphism
Dynamic Method Dispatch is the mechanism that resolves a call to an overridden method at runtime rather than at compile time. When you call a method on an object through a superclass reference, the JVM checks the actual type of the object in memory and executes the method belonging to that specific object.
The Telusko channel has an excellent video that breaks this down.
#56 Dynamic Method Dispatch in Java
This video provides a step-by-step walkthrough of dynamic method dispatch, using simple classes and a helpful memory diagram.
Please watch the following segments: Parent Reference, Child Object: This section explains why it's possible to have a superclass reference point to a subclass object, using a great Computer/Laptop analogy. The Dispatch in Action: Here, you'll see that calling the show() method on the parent reference actually executes the subclass's overridden version. Memory Visualization: This is a crucial part. The instructor illustrates with stack and heap diagrams how the same reference variable can point to different objects at different times, leading to different method calls. This should be quite intuitive given your CSE background. Extending to Multiple Subclasses: This demonstrates the true power of polymorphism—the same reference type can handle objects of multiple different subclasses, and the correct behavior is dispatched dynamically.
As the video clearly shows, the decision of which show() method to call is made at runtime, depending on whether the obj reference is pointing to an object of type A, B, or C. This is runtime polymorphism.
3. A Practical Example and Its Implications
Let's solidify this with another clear, text-based example that you can study and refer back to.
Dynamic Method Dispatch In Java (Runtime Polymorphism ...
The article from Unstop provides another classic example using an Animal hierarchy that perfectly illustrates the concept.
Please read the section titled "Dynamic Method Dispatch In Java Example: Animal". Notice how animal1 and animal2 are both of type Animal, but their makeSound() calls produce different outputs.
This ability to write code against a general superclass and have it work correctly with any number of specific subclasses is a cornerstone of good object-oriented design. It allows you to build systems that are easily extensible. Imagine you have a method performSound(Animal animal). You could pass any object that extends Animal to it (Dog, Cat, Lion, etc.), and the method would work without any changes.
However, as the article points out, if a method is not overridden in the subclass, the superclass's version is simply inherited and executed. Dynamic dispatch only applies to methods that are actually overridden.
4. Why It's Called Dynamic Binding: The Role of static
In the previous lesson, we touched on compile-time (static) versus runtime (dynamic) polymorphism. Now we can see exactly why dynamic dispatch is a form of runtime polymorphism. The binding of the method call to the method body happens dynamically at runtime.
This does not apply to all methods. Methods that are private, final, or, most importantly, static are resolved at compile time. This is called static binding. Since static methods belong to the class, not the object instance, the compiler binds the call based on the reference type, not the runtime object type.
This is a frequent interview question, and understanding the distinction is vital.
Dynamic Method Dispatch In Java (Runtime Polymorphism ...
This section of the Unstop article provides a fantastic side-by-side comparison of static and dynamic binding with clear code examples.
Please read the entire section "Static vs. Dynamic Binding & Dynamic Method Dispatch In Java". Pay very close attention to the two final code examples: Static Binding Example: See how employee1.getRole() calls the Employee version even though the object is a Manager. This is because getRole() is static. Dynamic Binding Example: See how removing the static keyword makes the exact same code call the Manager and Engineer versions correctly.
Conclusion
In this lesson, we have seen how method overriding enables one of the most powerful features of OOP: runtime polymorphism. This allows for the creation of flexible and extensible code where decisions about behavior are deferred until runtime.
Here are the key takeaways:
- Upcasting is the implicit casting of a subclass object to a superclass reference. It's what makes runtime polymorphism possible in this context.
- Dynamic Method Dispatch is the runtime mechanism where the JVM selects the appropriate overridden method based on the object's actual type, not the reference's type.
- The reference type determines which methods you are allowed to call (a compile-time check).
- The object's actual type determines which implementation of an overridden method gets executed (a runtime check).
- Instance methods use dynamic binding, while
static,final, andprivatemethods use static binding.
We've focused on how to handle different object types through a common superclass. But what if the superclass itself is too general to be instantiated? What if it's merely a "blueprint" that defines a contract for its subclasses? In our next lesson, we will explore this idea by delving into abstract classes and methods.