Java Polymorphism: Overloading, Overriding, and Dynamic Dispatch
Learn Java polymorphism with runnable examples of overriding and overloading, declared versus runtime types, null overloads, static hiding, and interface contracts.
Last reviewed: November 2026
🔍 What Is It?
Java has two types of polymorphism:
1. Runtime Polymorphism (Dynamic Dispatch)
Method overriding — the most specific implementation is called based on the actual runtime type. This is what most people mean by polymorphism.
2. Compile-time Polymorphism (Method Overloading)
Multiple methods with the same name but different parameter lists. The compiler selects the correct one based on the argument types.
Key Mechanism:
The language defines observable dispatch behavior, not one mandatory lookup structure. A JVM may use method tables, inline caches, or optimized direct calls while preserving the specified method selection rules.
Overloading versus overriding
| Question | Overloading | Overriding |
|---|---|---|
| What changes? | Parameter types or arity | Implementation of an inherited instance method |
| When is the signature chosen? | Compile time | The signature was already chosen at compile time |
| What decides the implementation? | The selected declaration; instance dispatch may still apply | Runtime receiver type |
| Can only the return type distinguish methods? | No | Covariant reference returns are permitted |
Covariant Return Types:
A subclass can override a method and return a more specific type than the parent declared.
Dispatch limits
- →Static methods are hidden, not overridden; use the declaring class name to call them clearly.
- →Fields are selected by the reference's compile-time type, not dynamically dispatched.
- →Private methods are not inherited as override targets; final methods cannot be overridden.
- →Constructors are not overridden.
- →Runtime overriding can be observed through either a parent/interface reference or a concrete reference; a parent-typed variable is a useful demonstration, not a requirement.
🎯 Why Do We Need It?
Polymorphism is the foundation of extensible software:
- →Open/Closed Principle — code is open for extension (add new types) but closed for modification (don't change existing code)
- →Dependency Inversion — depend on abstractions (interfaces), not concrete classes
- →Strategy Pattern — swap algorithms at runtime by swapping implementations
- →Framework design — a Spring client can depend on an interface while a configured implementation supplies the behavior
Without polymorphism, adding a new payment type requires modifying every place that processes payments. With polymorphism, you add a new class and the existing code works automatically.
🏗️ What Happens in Real Projects?
Every Spring Boot application uses polymorphism constantly:
- →
@Serviceclasses implement service interfaces — controllers depend on the interface - →
@Repositoryextends JpaRepository — Spring generates the implementation at runtime - →Exception handlers use polymorphism — catch(Exception e) catches all subtypes
- →Notification systems — EmailNotifier, SMSNotifier, PushNotifier all implement Notifier
⚙️ How It Works
Method invocation has two important stages:
- At compile time, Java checks the reference type, accessible declarations, argument types, and overload rules to select a method signature.
- At runtime, an ordinary overridden instance method is dispatched using the receiver object's actual class. Static calls do not use this dispatch rule.
If Person person = new Student(), then person.speak() can execute Student's override. But an overloaded describe(person) is selected using the declared Person argument type; Java does not switch to a Student overload merely because the argument holds a Student object.
For overloads accepting int, Integer, and long, process(null) selects the reference-type Integer overload. Null cannot be passed directly to a primitive parameter. With unrelated reference overloads such as String and Integer, select(null) is ambiguous unless an explicit type disambiguates it.
💻 Example
Polymorphism in a Notification System — How polymorphism makes adding new notification channels trivial.
🤔 Common Confusions
❓ What is the difference between overriding and overloading?
Overloading defines same-named methods with different parameter lists and can include inherited methods. Overload selection occurs at compile time. Overriding supplies a compatible implementation of an inherited instance method; runtime receiver type chooses the implementation of the selected signature. Use @Override to have the compiler check an intended override.
❓ Can static methods be polymorphic?
No. Static methods are resolved at compile time based on the declared reference type, not the actual runtime type. This is called method hiding, not overriding. If you call Animal.speak() vs Dog.speak(), you always get the declared type's version. This is why polymorphism requires instance methods.
❓ What is the difference between instanceof and polymorphism?
An interface method can replace repeated type checks when each implementation owns the behavior. instanceof or pattern matching can still be appropriate at boundaries or when working with a closed set of variants. Choose according to who owns the behavior rather than treating every type test as an error.
🚨 Common Production Issues
instanceof chains instead of polymorphism — long if/else instanceof chains violate OCP and become unmaintainable. Refactor to polymorphism.
Overriding equals in a hierarchy — if Parent.equals() is based on type, subclass instances can break symmetry (a.equals(b) is true but b.equals(a) is false). Effective Java recommends against overriding equals in subclasses that add fields.
Covariant return type confusion — a method returning a subtype can confuse callers who expect the parent type. Document clearly.
✅ Best Practices
Program to interfaces, not concrete classes — List<String> not ArrayList<String>.
Use polymorphism to eliminate instanceof chains.
Keep the Liskov Substitution Principle — subclasses must be substitutable for parent.
Prefer interface-based polymorphism over class-based inheritance polymorphism.
Use @Override always — it catches signature mismatches at compile time.
🏛️ Architect's Note
An interface can separate a caller from interchangeable implementations, which is useful for strategies, external integrations, and testing. Spring can inject either interfaces or concrete classes; an interface is a design choice, not a dependency-injection requirement. Keep each implementation substitutable for the contract, including failures and side effects, rather than only matching method signatures.
💬 Frequently Asked Questions
🎯 Key Takeaways
🔗 Related Topics
🎯 Practice this topic in interviews
Core Java Interview Prep
Practice interview questions that connect directly to this technology track.
↗ Where this topic fits
Core Java Technology Guide
Return to the Core Java learning hub.
Backend Engineer Career Path
A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.
Full Stack Java Developer Career Path
A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.
Sources
Sources / References
Use these Core Java references to verify the concept in official docs and continue into the most relevant SplashRide pages.
Official documentation
Related SplashRide pages
SplashRide Core Java learning path
Browse the full Core Java topic collection and learning order on SplashRide.
Core Java Interview Prep
Practice Core Java interview questions and scenario-based answers.
Backend Engineer Career Path
A structured journey through Core Java, Spring Boot, production backend engineering, and interview preparation.
Full Stack Java Developer Career Path
A guided route through frontend foundations, Java backend engineering, delivery workflows, and interview readiness.
Inheritance
How Java inheritance works — extends, super, method overriding, and when to use inheritance vs composition in real enterprise projects.
Abstraction
How abstract classes and interfaces define contracts in Java — when to use each, default methods, and how abstraction enables the SOLID principles.