Mastering Java Beyond Classes — Part 7: Polymorphism in Java
Polymorphism is one of the core pillars of Object-Oriented Programming and also one of the most misunderstood.
One interface, multiple implementations?
…but fail when asked:
- What actually happens at runtime?
- Why does method overloading behave differently?
- What does the JVM really call?
This article fixes that.
What Is Polymorphism?
Polymorphism uses the same method name, different behaviour. There are two types in Java:

Compile-Time Polymorphism (Method Overloading)
Same method name, different parameters.
class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
}
public class TestOverloading {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(2, 3));
System.out.println(calc.add(2.5, 3.5));
}
}
Output
5
6.0
Runtime Polymorphism (Method Overriding)
Now we’re talking real polymorphism.
class Animal {
void sound() {
System.out.println("Animal makes sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
public class TestPolymorphism {
public static void main(String[] args) {
Animal obj = new Dog();
obj.sound();
}
}
Output
Dog barks
What’s Really happening?
- Reference type — Animal
- Object type — Dog
- JVM decides the method at runtime
This is called: Dynamic Method Dispatch
Method calls depend on the OBJECT type
Variable access depends on the REFERENCE type
Look at the following example. What is the Output?
class Parent {
int value = 10;
void show() {
System.out.println("Parent show()");
}
}
class Child extends Parent {
int value = 20;
@Override
void show() {
System.out.println("Child show()");
}
}
public class Test {
public static void main(String[] args) {
Parent obj = new Child();
System.out.println(obj.value);
obj.show();
}
}
Output
10
Child show()
Explanation
- obj.value — uses Parent reference
- obj.show() — uses Child object
Upcasting and Downcasting
Upcasting (Safe)
Animal a = new Dog();
- Done automatically
- Enables polymorphism
Downcasting (Risky)
Dog d = (Dog) a;
- Requires explicit cast
- Can cause a runtime error
Dangerous Example
Animal a = new Animal();
Dog d = (Dog) a; // Runtime error
ClassCastException
Real-World Example
interface Payment {
void pay();
}
class CreditCard implements Payment {
public void pay() {
System.out.println("Paid using Credit Card");
}
}
class PayPal implements Payment {
public void pay() {
System.out.println("Paid using PayPal");
}
}
public class PaymentDemo {
public static void main(String[] args) {
Payment p;
p = new CreditCard();
p.pay();
p = new PayPal();
p.pay();
}
}
Output
Paid using Credit Card
Paid using PayPal
Same interface → different behavior
This is real-world polymorphism

Practice Questions
Question 1
What will be the output?
class A {
void show() {
System.out.println("A");
}
}
class B extends A {
void show() {
System.out.println("B");
}
}
public class Test {
public static void main(String[] args) {
A obj = new B();
obj.show();
}
}
Question 2
What will be the output?
class Test {
void show(int a) {
System.out.println("int");
}
void show(double a) {
System.out.println("double");
}
public static void main(String[] args) {
new Test().show(10);
}
}
Question 3
What will be the output?
class Parent {
static void show() {
System.out.println("Parent");
}
}
class Child extends Parent {
static void show() {
System.out.println("Child");
}
}
public class Test {
public static void main(String[] args) {
Parent obj = new Child();
obj.show();
}
}
Bonus Challenge
What is the output?
class Animal {
void run() {
System.out.println("Animal");
}
}
class Dog extends Animal {
void run() {
System.out.println("Dog");
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Dog();
((Animal)a).run();
}
}
Common Mistakes
- Thinking overloading = runtime polymorphism
- Forgetting @Override annotations
- Confusing variable vs method behavior
- Downcasting blindly
- Expecting static methods to behave polymorphically
Best Practices
- Always use interfaces and polymorphism
- Prefer runtime polymorphism over conditionals
- Use @Override to avoid silent bugs
- Avoid unnecessary casting
- Write code like:
List<String> list = new ArrayList<>();
Program to interfaces, not implementations.
If you found this article helpful:
- Follow for more Java tutorials
- Share your answers in the comments
- Save this guide for future reference
Conclusion
Polymorphism is far more than an interview topic — it is one of the foundations of scalable software design.
It allows developers to write flexible, reusable, and maintainable code by programming to abstractions rather than concrete implementations.
Throughout this article, we explored:
- Compile-time polymorphism (method overloading)
- Runtime polymorphism (method overriding)
- Dynamic method dispatch
- Upcasting and downcasting
- Real-world interface-based polymorphism
- Common pitfalls and best practices
Final Rule to Remember
Methods are resolved by object type.
Variables are resolved by reference type.
If you understand that rule deeply, you understand the heart of Java polymorphism.
So this is the end of my Mastering Java Beyond Classes series. See you in the next series, where we dive deeper into advanced Java.
Thank you!
Have a great day!
Mastering Java Beyond Classes — Part 7: Polymorphism in Java was originally published in Javarevisited on Medium, where people are continuing the conversation by highlighting and responding to this story.
This post first appeared on Read More

