Polymorphism in Java
Polymorphism in Java
Inheritance lets a Dog and a Cat both be treated as an Animal. Polymorphism is what makes that actually useful: it lets the same method call produce different behaviour depending on which object is involved, without the calling code needing to know or care which specific subclass it is dealing with.
What is Polymorphism?
Polymorphism literally means 'many forms'. In Java, it describes a situation where the same method name can behave differently depending on either the arguments passed to it, or the actual object type it is called on at runtime.
Picture a veterinary clinic where every animal, regardless of species, responds to a single instruction: makeSound(). A dog barks, a cat meows, a cow moos, the instruction is identical, but each animal answers it in its own way. That is polymorphism in a single sentence.
Java supports two distinct flavours of this idea, and the next two sections walk through each in turn.
Compile-Time Polymorphism (Method Overloading)
Compile-time polymorphism, also called static polymorphism, happens when a class defines multiple methods that share the same name but differ in their parameter list, either in the number of parameters, their types, or both. The Java compiler decides which version to run by looking at the arguments at compile time, before the program ever executes.
class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
}
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(2, 3)); // 5 — calls the int version
System.out.println(calc.add(2.5, 3.5)); // 6.0 — calls the double version
System.out.println(calc.add(1, 2, 3)); // 6 — calls the 3-argument version
}
}
Notice that the compiler never has to guess at runtime here. The number and type of arguments in calc.add(2, 3) are already enough information, fixed at compile time, to know exactly which add() method is meant.
Runtime Polymorphism (Method Overriding)
Runtime polymorphism, also called dynamic polymorphism, happens when a subclass redefines a method that it inherited from its superclass, giving it new behaviour. Java decides which version to run only at runtime, by checking the actual object's real type, not the type of the variable holding the reference.
class Animal {
void makeSound() {
System.out.println("Some generic animal sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Woof!");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Meow!");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal;
myAnimal = new Dog();
myAnimal.makeSound(); // Woof! — decided at runtime
myAnimal = new Cat();
myAnimal.makeSound(); // Meow! — decided at runtime
}
}
Even though myAnimal is declared as type Animal in both cases, Java looks at what the variable actually points to when makeSound() is called, a Dog the first time, a Cat the second, and runs that object's own version of the method. This behaviour is also called dynamic method dispatch.
Aspect | Compile-Time Polymorphism | Runtime Polymorphism |
Achieved through | Method overloading | Method overriding |
Decided by | The compiler, based on argument types | The JVM, based on the object's actual type |
Relationship needed | None — works within a single class | Requires inheritance between classes |
When resolved | Before the program runs | While the program is running |
Quick Tip
Always add the @Override annotation when overriding a method. It does not change how the program runs, but it tells the compiler to check that you are genuinely overriding an existing superclass method, catching typos in the method name or parameter list that would otherwise silently create an unrelated new method instead.










