Dynamic Method Dispatch is the mechanism Java uses to select an overridden instance method at runtime. It is one of the most important mechanisms behind runtime polymorphism.
The basic idea is simple: a parent class or interface reference can point to different child objects, and when an overridden instance method is called, Java determines which implementation should execute based on the actual object stored in the reference.
Core idea: The reference type determines what the compiler allows you to call, while the actual runtime object determines which overridden instance method implementation executes.
Why Is Dynamic Method Dispatch Important?
Consider a system that supports different payment methods. Every payment can perform a pay() operation, but credit cards, UPI, and wallets may implement that operation differently.
The calling code should not need to know the concrete payment type. It should work with the common Payment type and let Java dispatch the correct implementation.
class Payment { void pay() { System.out.println("Processing payment"); } } class CardPayment extends Payment { @Override void pay() { System.out.println("Processing card payment"); } } class UpiPayment extends Payment { @Override void pay() { System.out.println("Processing UPI payment"); } } public class Main { public static void main(String[] args) { Payment payment = new CardPayment(); payment.pay(); payment = new UpiPayment(); payment.pay(); } }
The first call executes CardPayment.pay(). After the reference is assigned a different object, the second call executes UpiPayment.pay().
The method call remains payment.pay() in both cases. What changes is the actual object referenced at runtime.
The Key Pattern
Dynamic method dispatch is commonly demonstrated using this pattern:
Parent reference = new Child();
For example:
Animal animal = new Dog();
animal.sound();
Here, Animal is the reference type and Dog is the runtime object type. If Dog overrides sound(), the Dog implementation is selected when the method is invoked.
Remember this sentence: Dynamic method dispatch chooses the overridden instance method according to the actual object at runtime.
Step-by-Step Execution
Consider the following code:
class Animal { void sound() { System.out.println("Animal sound"); } } class Dog extends Animal { @Override void sound() { System.out.println("Dog barks"); } } Animal animal = new Dog(); animal.sound();
- The variable animal is declared with the reference type Animal.
- The new Dog() expression creates a Dog object.
- The Dog object is assigned to the Animal reference.
- The compiler verifies that sound() is available through Animal.
- At runtime, Java identifies the actual object as Dog.
- Because Dog overrides sound(), the Dog.sound() implementation executes.
This final step is the essence of dynamic method dispatch.
Reference Type vs Runtime Object
| Part | Example | Role |
|---|---|---|
| Reference type | Animal | Controls what members are available through the reference at compile time |
| Runtime object | Dog | Determines the overridden instance method implementation used at runtime |
This distinction is fundamental. Beginners often see Animal animal and assume Java will always execute the Animal implementation. That is not how overridden instance methods work.
Multiple Child Classes
Dynamic dispatch becomes particularly useful when several child classes override the same method.
class Shape { void draw() { System.out.println("Drawing shape"); } } class Circle extends Shape { @Override void draw() { System.out.println("Drawing circle"); } } class Rectangle extends Shape { @Override void draw() { System.out.println("Drawing rectangle"); } } public class Main { public static void main(String[] args) { Shape shape; shape = new Circle(); shape.draw(); shape = new Rectangle(); shape.draw(); } }
The reference variable shape has the same type throughout the example. However, the object changes from Circle to Rectangle, so the executed method changes accordingly.
Dynamic Dispatch Through a Method Parameter
One of the strongest practical uses of dynamic dispatch is passing different child objects to a method that accepts the parent type.
class Notification { void send() { System.out.println("Sending notification"); } } class EmailNotification extends Notification { @Override void send() { System.out.println("Sending email"); } } class SmsNotification extends Notification { @Override void send() { System.out.println("Sending SMS"); } } class NotificationService { static void sendNotification(Notification notification) { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService.sendNotification( new EmailNotification() ); NotificationService.sendNotification( new SmsNotification() ); } }
The service knows only about Notification. It does not need separate methods for every notification type. Dynamic dispatch ensures that the correct implementation executes.
Industry insight: This design is a foundation for extensible systems. New implementations can often be introduced without changing the code that operates on the common abstraction.
Dynamic Dispatch with Interfaces
The same mechanism is commonly used with interfaces.
interface Payment { void pay(); } class CardPayment implements Payment { @Override public void pay() { System.out.println("Card payment"); } } class UpiPayment implements Payment { @Override public void pay() { System.out.println("UPI payment"); } } Payment payment = new CardPayment(); payment.pay(); payment = new UpiPayment(); payment.pay();
The reference is of interface type Payment, but the runtime object changes. The correct implementation is selected for each object.
Dynamic Dispatch in Collections
Collections provide another practical demonstration because a collection can store different implementations through a common parent or interface type.
Payment[] payments = {
new CardPayment(),
new UpiPayment()
};
for (Payment payment : payments) {
payment.pay();
}
The loop treats every element as a Payment, but each object's own implementation of pay() executes.
This is much cleaner than checking every object's type manually.
Dynamic Dispatch vs Method Overloading
A very common interview trap is confusing dynamic method dispatch with method overloading. They solve different problems and use different mechanisms.
| Feature | Dynamic Method Dispatch | Method Overloading |
|---|---|---|
| Associated concept | Runtime polymorphism | Compile-time polymorphism |
| Usually involves | Method overriding | Multiple methods with different parameter lists |
| Decision | Runtime | Compile time |
| Main factor | Actual runtime object | Method arguments and available signatures |
| Typical pattern | Parent ref = new Child() | Same method name with different parameters |
Overloaded and Overridden Methods Together
A class hierarchy can contain both overloaded and overridden methods. This is where understanding compile-time and runtime decisions becomes especially important.
class Parent { void show(int value) { System.out.println("Parent int"); } void show(String value) { System.out.println("Parent String"); } } class Child extends Parent { @Override void show(int value) { System.out.println("Child int"); } } Parent object = new Child(); object.show(10); object.show("Hello");
For show(10), the compiler first identifies the matching signature, show(int). At runtime, it then dispatches the call to the overridden Child.show(int).
For show("Hello"), the matching signature is show(String). Since the child has not overridden that method, the inherited parent implementation executes.
Important mental model: Overload selection happens first at compile time; dynamic dispatch of an overridden instance method happens at runtime.
What Dynamic Dispatch Does Not Apply To
Dynamic method dispatch applies to overridden instance methods. It does not work the same way for static methods, private methods, or final methods.
class Parent { static void show() { System.out.println("Parent static"); } } class Child extends Parent { static void show() { System.out.println("Child static"); } }
The static methods above are hidden rather than dynamically dispatched. Their behavior follows class/reference resolution rules rather than the runtime object selection used for overridden instance methods.
Why Dynamic Dispatch Improves Design
Dynamic dispatch helps separate what an object can do from how that behavior is implemented. The calling code can work against a common abstraction while individual classes provide their own behavior.
This is particularly valuable in enterprise applications, where payment providers, notification channels, storage implementations, report generators, authentication strategies, and many other components may share a common contract but require different implementations.
Common Beginner Mistakes
- Thinking the reference type determines the overridden method implementation.
- Confusing dynamic dispatch with method overloading.
- Assuming child-specific methods are accessible through a parent reference.
- Assuming static methods participate in dynamic dispatch.
- Forgetting that the child must actually override the method for different runtime behavior to occur.
- Using explicit type checks everywhere instead of taking advantage of polymorphic behavior.
Best Practices
- Use parent classes or interfaces as abstractions when multiple implementations share a meaningful contract.
- Use @Override to make overriding explicit and catch signature errors.
- Keep polymorphic methods focused on behavior that genuinely varies between implementations.
- Avoid unnecessary instanceof checks when dynamic dispatch can express the behavior more naturally.
- Design APIs so callers depend on abstractions rather than concrete implementations where appropriate.
Interview Insights
A common interview question is: "What is dynamic method dispatch in Java?"
A strong answer is: Dynamic method dispatch is the runtime mechanism by which Java selects the implementation of an overridden instance method based on the actual object referenced by a parent class or interface reference.
Another common question is: "What is the difference between dynamic dispatch and method overloading?" The key distinction is that overloading is resolved at compile time based primarily on the method arguments, while dynamic dispatch selects an overridden instance method at runtime based on the actual object.
Final Takeaway
Dynamic Method Dispatch is the engine behind Java's runtime polymorphism. When a parent or interface reference points to a child object, Java can select the child's overridden instance method when that method is called. The most useful pattern to remember is Parent reference = new Child(). Understand the difference between the reference type and runtime object type, and dynamic dispatch becomes much easier to reason about—and the upcoming concepts of upcasting and downcasting will fit naturally into that picture.
