Example of Polymorphism in Java: Types, Advantages, Pitfalls, and More
Updated on Feb 13, 2025 | 16 min read | 43.5k views
Share:
For working professionals
For fresh graduates
More
Updated on Feb 13, 2025 | 16 min read | 43.5k views
Share:
Table of Contents
Imagine being able to trigger different behaviors in your program using the exact same command. That’s the essence of polymorphism in Java. The word polymorphism stems from the Greek words poly (many) and morph (forms), and in OOP (object-oriented programming), it allows a single interface — like a method name — to adapt and take on multiple distinct behaviors.
You’ll see the example of polymorphism in Java in everyday objects, too. For instance, a smartphone can serve as a camera, music player, or calculator, yet it remains the same phone. Much like one person can be both a father and an employee (responding differently depending on whether he’s at home or work), Java objects can wear different hats while still sharing a common blueprint.
In this blog post, you’ll explore examples of polymorphism in Java, how polymorphism works, and its two main types (compile-time and runtime).
Polymorphism in Java means using one method name or interface to perform different tasks based on the specific class or object involved. It hinges on Java inheritance and interfaces, which let you group shared features in a parent class (or interface) and override or implement them in child classes.
Below, you’ll see a sample program demonstrating how one method call can produce different outcomes, all thanks to polymorphism.
This is the core of polymorphism: you call print(), and Java decides which version to run at runtime, depending on the actual object.
class Printer {
void print() {
System.out.println("Standard printing");
}
}
class PhotoPrinter extends Printer {
void print() {
System.out.println("High-resolution photo printing");
}
}
class DocumentPrinter extends Printer {
void print() {
System.out.println("Fast text printing");
}
}
public class PrintDemo {
public static void main(String[] args) {
Printer p1 = new Printer();
Printer p2 = new PhotoPrinter();
Printer p3 = new DocumentPrinter();
p1.print(); // Standard printing
p2.print(); // High-resolution photo printing
p3.print(); // Fast text printing
}
}
Let’s take another real-life example of polymorphism in Java now.
Think of a single pass that covers different modes of transportation. You keep one pass in your wallet, yet you behave differently:
The pass itself stays the same, but each transport option recognizes it differently. That’s similar to polymorphism in Java, where one reference point can take multiple forms.
Below is a parent class called Pass and child classes for buses, trains, and metros.
class Pass {
void validate() {
System.out.println("General pass validation");
}
}
class BusPass extends Pass {
void validate() {
System.out.println("Bus pass validated through onboard scanner");
}
}
class TrainPass extends Pass {
void validate() {
System.out.println("Train pass validated at station counter");
}
}
class MetroPass extends Pass {
void validate() {
System.out.println("Metro pass validated at electronic gate");
}
}
public class TransportDemo {
public static void main(String[] args) {
Pass myPass = new Pass();
Pass busPass = new BusPass();
Pass trainPass = new TrainPass();
Pass metroPass = new MetroPass();
myPass.validate();
busPass.validate();
trainPass.validate();
metroPass.validate();
}
}
Here, the validate() call appears the same for every object. However, each subclass (BusPass, TrainPass, and MetroPass) provides its own take on this method. That shows how polymorphism lets you reuse a single reference type while still allowing different behaviors.
For more clarity, check out upGrad’s free tutorial, Pass By Value and Call By Reference in Java.
Polymorphism in Java commonly falls into two groups: compile-time and runtime polymorphism. Both forms revolve around the same core principle – a single interface (or method name) can serve many specialized purposes.
To understand how this works, it helps to keep a couple of things in mind.
Moving forward, you’ll see how each type of polymorphism (compile-time and runtime) uses these ideas to provide different methods of achieving “one name, many forms.”
Runtime polymorphism, sometimes known as dynamic method dispatch, lets the Java Virtual Machine decide which overridden method to call when your code runs. This concept closely relates to what other languages label as virtual functions, where child classes can override a base class method, and the actual method depends on the object type at runtime.
In Java, non-static instance methods act like virtual functions by default, which means you don’t need extra keywords to make them dynamically dispatched.
Method Overriding and Its Connection to Runtime Polymorphism
Method overriding happens when a child class defines a method with the same name, return type, and parameters as a method in its parent class. Once you store a child object inside a parent reference, Java decides which version of the method to execute based on the child’s actual class.
That is runtime polymorphism in action: the same method call adapts to whichever overridden version belongs to the real object under the parent reference.
Let’s understand run time polymorphism in Java with examples now.
Example 1: Implementing Runtime Polymorphism in Java
class Animal {
void sound() {
System.out.println("Some generic animal sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Bark!");
}
}
public class Example1 {
public static void main(String[] args) {
Animal myPet = new Dog();
myPet.sound(); // Bark!
}
}
Output: When myPet.sound() runs, the program calls the sound() method from Dog because that class overrides the method in Animal.
Bark!
Example 2: Implementing Runtime Polymorphism in Java
class Vehicle {
void run() {
System.out.println("Some generic vehicle is running");
}
}
class Car extends Vehicle {
@Override
void run() {
System.out.println("Car is running on four wheels");
}
}
class Bike extends Vehicle {
@Override
void run() {
System.out.println("Bike is running on two wheels");
}
}
public class Example2 {
public static void main(String[] args) {
Vehicle v1 = new Car();
Vehicle v2 = new Bike();
v1.run(); // Car is running on four wheels
v2.run(); // Bike is running on two wheels
}
}
Output: The run() calls adapt to the correct subclass based on the actual object stored in v1 and v2.
Car is running on four wheels
Bike is running on two wheels
Example 3: Implementing Runtime Polymorphism in Java
class Account {
float rateOfInterest() {
return 0.0f;
}
}
class SavingsAccount extends Account {
@Override
float rateOfInterest() {
return 4.0f;
}
}
class FixedDeposit extends Account {
@Override
float rateOfInterest() {
return 6.5f;
}
}
public class Example3 {
public static void main(String[] args) {
Account acct1 = new SavingsAccount();
Account acct2 = new FixedDeposit();
System.out.println("Savings interest: " + acct1.rateOfInterest() + "%");
System.out.println("Fixed deposit interest: " + acct2.rateOfInterest() + "%");
}
}
Output: Because both subclasses override rateOfInterest(), you can maintain a single Account reference type yet still capture each specific behavior at runtime.
Savings interest: 4.0%
Fixed deposit interest: 6.5%
Compile-time polymorphism, sometimes called static binding, lets the Java compiler decide which method to call before the program runs. The language achieves this through method overloading, where multiple methods share the same name but differ by the types or number of parameters. As a result, the compiler picks the right method by analyzing the arguments in each call, and it locks in that choice early.
And as you already know, Java doesn’t allow custom operator overloading, although it does treat the + operator in a special way for numbers and strings. That remains the only built-in operator that handles two roles, performing addition for numbers and concatenation for strings.
Let’s take a look at some examples of compile-time polymorphism in Java.
Example 1: Overloading by Changing the Number of Parameters
class Calculator {
int add(int a, int b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
}
public class CompileTimeExample1 {
public static void main(String[] args) {
Calculator calc = new Calculator();
// Here, the compiler sees two parameters, so it calls add(int a, int b)
System.out.println("Result of adding two numbers: " + calc.add(5, 10));
// Here, the compiler sees three parameters, so it calls add(int a, int b, int c)
System.out.println("Result of adding three numbers: " + calc.add(2, 3, 4));
}
}
Output: When you use two arguments, you trigger the two-parameter version. With three, the three-parameter version runs. This decision is locked in during compilation.
Result of adding two numbers: 15
Result of adding three numbers: 9
Example 2: Overloading by Changing Parameter Types
class MathOperations {
int multiply(int x, int y) {
return x * y;
}
double multiply(double x, double y) {
return x * y;
}
}
public class CompileTimeExample2 {
public static void main(String[] args) {
MathOperations ops = new MathOperations();
// The compiler knows to call multiply(int x, int y)
System.out.println("Multiplying integers: " + ops.multiply(3, 4));
// The compiler knows to call multiply(double x, double y)
System.out.println("Multiplying doubles: " + ops.multiply(2.5, 4.0));
}
}
Output: The compiler resolves each method call ahead of time by matching the argument types with the correct method signature.
Multiplying integers: 12
Multiplying doubles: 10.0
Example 3: Operator Overloading for + (Numbers vs Strings)
public class CompileTimeExample3 {
public static void main(String[] args) {
// When adding integers, the compiler picks numeric addition
int numResult = 10 + 5;
System.out.println("Number addition: " + numResult);
// When using strings, the compiler treats + as concatenation
String textResult = "Hello" + " " + "World";
System.out.println("String concatenation: " + textResult);
}
}
Output: The compiler determines that + between integers performs standard addition, while + with strings merges the text. This is an internal language feature that stands out as the only built-in operator overloading in Java.
Number addition: 15
String concatenation: Hello World
Curious to know more about examples of Polymorphism in Java? Start your journey with upGrad’s Core Java course today and gain essential skills!
Both styles of polymorphism handle the idea of giving one name to multiple forms, yet the moment you fix which form to use sets them apart.
With run time polymorphism, Java decides which method applies while the program is active, based on the actual objects involved. On the other hand, compile-time polymorphism resolves everything before your code starts running.
Here’s a table that highlights the main differences:
Aspect |
Runtime Polymorphism in Java |
Compile-time Polymorphism in Java |
Alternate Name | Dynamic method dispatch | Static binding |
Key Mechanism | Method overriding | Method overloading |
Decision Time | Determined during program execution | Determined by the compiler before runtime |
Inheritance Need | Requires a parent-child relationship | Can happen in the same class; no inheritance needed |
Speed | Often a bit slower (dynamic checks) | Generally faster (compiler knows in advance) |
Focus | Subclasses redefine a parent’s method | Methods differ in parameters or argument types |
Example Usage | Calling a shared method on various subclasses of a base class. | Having multiple versions of a method name within one class (e.g., different parameter lists). |
Also Read: Difference Between Overloading and Overriding in Java: Understanding the Key Concepts in 2025
Polymorphism in Java lets you shape methods and classes so they adapt to new tasks without endless duplication. This simplifies your work when refining old code to meet fresh demands or unifying multiple actions under one consistent name. It’s a principle that brings order and clarity to projects of all sizes.
Below are some concrete advantages, each highlighting how polymorphism smooths out your coding experience:
Also Read: What are the Advantages of Object-Oriented Programming?
Polymorphism doesn’t always deliver smooth sailing. When it’s taken too far or applied haphazardly, you might encounter deeper layers of confusion and performance slowdowns. Knowing these drawbacks helps you decide when to rely on polymorphism and when to consider simpler solutions.
Below are some typical disadvantages you may face:
Polymorphism shows up across many fields, where it supports fresh updates in software without forcing major rewrites. By enabling classes to share a baseline while still tailoring their inner workings, development teams can adapt programs to new requirements more smoothly.
Below is a table that covers several industries and how they put polymorphism into action:
Industries |
Example Use Cases |
IT & Software Development | - Framework plug-ins implement a common interface yet override methods to integrate seamlessly. - Microservices share a base contract, while each has unique logic for handling requests. |
Banking, Financial Services, Insurance | - Multiple account classes inherit from a single Account type, each overriding methods like calculateInterest(). - Loan objects switch interest formulas based on dynamic criteria. |
E-commerce and Retail | - Payment classes (CreditCard, UPI, NetBanking) override a common interface for pay(). - Shipping strategies adapt a shared ShippingMethod parent to handle varied logistics. |
Healthcare | Patient records rely on a unified structure, with child classes for different specialities overriding methods to format and analyze data. |
Gaming | - Characters derived from a base Character class override behavior such as attack() or move(). - Inventory items share one interface but differ in usage and effects. |
Automotive | A common Vehicle parent leads to Car, Bike, and Truck classes, each with custom run() or fuelUp() methods. |
Telecom | - Plans inherit from a single Plan blueprint, overriding data quotas and billing details. - Service providers implement the same interface but set their own connection parameters. |
Manufacturing | Machines share an overarching Machine class but override methods for specialized tasks (e.g., drilling, cutting). |
Educational Technology | Course content inherits from a generic Lesson or Module class, overriding how materials are delivered (video, quiz, or text). |
Software Tools | Plug-in architectures let each plug-in subclass override the same entry point in an application without major code changes. |
With over 1 million+ learners globally enhancing their skills, upGrad has established itself as a leader in programming education. Our programming courses combine industry-relevant curricula, hands-on projects, and mentorship to help you stay ahead in a competitive tech environment.
Here’s how upGrad supports your programming journey.
Not sure where to begin? upGrad offers personalized career counseling to help you choose the right course.
Boost your career with our popular Software Engineering courses, offering hands-on training and expert guidance to turn you into a skilled software developer.
Master in-demand Software Development skills like coding, system design, DevOps, and agile methodologies to excel in today’s competitive tech industry.
Stay informed with our widely-read Software Development articles, covering everything from coding techniques to the latest advancements in software engineering.
Get Free Consultation
By submitting, I accept the T&C and
Privacy Policy
India’s #1 Tech University
Executive PG Certification in AI-Powered Full Stack Development
77%
seats filled
Top Resources