Menu

Java Polymorphism: One Interface, Many Forms

How Java polymorphism lets one variable refer to many types, why overridden methods dispatch at runtime, and how to use upcasting, downcasting, and instanceof safely.

This page includes runnable editors - edit, run, and see output instantly.

One Reference, Many Types

Polymorphism is the payoff of inheritance and interfaces. It means a single variable typed as a parent (or an interface) can hold an object of any subtype, and when you call a method on it, Java runs the version belonging to the object's actual class - not the version implied by the variable's declared type.

You have already seen the setup on the inheritance page: a subclass overrides a method from its parent. Polymorphism is what makes that override worth doing - it lets you write code against the general type and let each concrete object behave in its own way.

Both a and b are declared as Animal, yet each prints its own sound. That choice happens at runtime based on the real object.

Dynamic Method Dispatch

The mechanism behind this is dynamic method dispatch: for an overridden instance method, the JVM looks at the object's runtime class to decide which implementation to call. The compiler only checks that the method exists on the declared type; the actual selection is deferred until the program runs.

This is what lets one loop handle a whole mix of types without ever asking what each one is:

The loop only knows about Shape. Add a Triangle extends Shape later and this code keeps working unchanged - that is the whole point. Code depends on the abstraction, not the concrete list of types.

Upcasting and Downcasting

Storing a Dog in an Animal variable is upcasting - moving up the hierarchy to a more general type. It is always safe and Java does it implicitly, because every Dog is an Animal.

Going the other direction is downcasting - taking a parent reference and treating it as a specific subtype. This is a form of type casting between reference types, and it is only valid if the object really is that subtype, so you must write the cast explicitly, and you risk a ClassCastException if you are wrong:

The last cast compiles fine - the compiler can't prove it's wrong - but blows up when it runs because a Cat is not a Dog. Never downcast on faith.

Guard Downcasts with instanceof

Before downcasting, check the real type with instanceof. Modern Java lets you bind the result in the same expression (pattern matching for instanceof), so you skip the separate cast:

instanceof returns false for null, so the check also protects you from a NullPointerException. That said, if you find yourself writing long instanceof chains, it is often a sign the behavior belongs inside the classes as an overridden method - let polymorphism do the branching for you.

Overriding vs Overloading

These two sound alike but are unrelated, and mixing them up is a classic source of confusion.

Overriding is a subclass replacing a parent method with the identical signature. It is resolved at runtime by the object's type - this is the polymorphism we have been using.

Overloading is one class having several methods of the same name but different parameter lists. It is resolved at compile time by the argument types, with no runtime dispatch involved:

The compiler picks the matching describe purely from the argument's static type. There is no parent/child object involved, so this is not runtime polymorphism - it just reuses a method name.

A Common Gotcha: Fields Are Not Polymorphic

Only instance methods are dispatched at runtime. Fields and static methods are resolved by the declared type, which trips up a lot of people:

p.name() runs Child's version (polymorphism), but p.label reads Parent's field, because fields are hidden, not overridden. The fix is simple: keep fields private and access them only through methods, so the polymorphic call always wins.

Next: Access Modifiers

Polymorphism only works cleanly when subclasses can see and override the right members while the rest of your code can't reach in and break invariants. That balance is controlled by public, protected, private, and package-private access - the access modifiers, up next.

Frequently Asked Questions

What is polymorphism in Java?

Polymorphism means one reference type can point to objects of many different classes, and the method that actually runs is chosen by the object's real type at runtime - not by the declared type of the variable. So a Shape shape variable can hold a Circle or a Square, and calling shape.area() runs the right version automatically.

What is the difference between overriding and overloading in Java?

Overriding is when a subclass replaces a superclass method with the same signature - this is what drives runtime polymorphism. Overloading is when one class has several methods with the same name but different parameter lists; the compiler picks one at compile time based on the arguments. Overriding is resolved at runtime by the object's type; overloading is resolved at compile time by the argument types.

What is the difference between upcasting and downcasting in Java?

Upcasting treats a child object as its parent type (Animal a = new Dog();) - always safe and usually implicit. Downcasting goes the other way (Dog d = (Dog) a;) and is only safe if the object really is that subtype; otherwise it throws ClassCastException. Guard every downcast with instanceof first.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED