Types of Polymorphism in Java [Static & Dynamic Polymorphism with Examples]
By Rohan Vats
Updated on Jun 13, 2025 | 21 min read | 78.63K+ views
Share:
For working professionals
For fresh graduates
More
By Rohan Vats
Updated on Jun 13, 2025 | 21 min read | 78.63K+ views
Share:
Table of Contents
Did You Know?
Spotify uses Java polymorphism to handle songs, podcasts, and ads through a single interface—keeping your streaming experience seamless and smart!
Polymorphism in Java is one of the key principles of object-oriented programming that enables a single method, class, or interface to take multiple forms. It allows developers to write cleaner, more modular code by letting the same interface behave differently depending on the underlying object or data.
This powerful concept is widely used across real-world applications—from simplifying transaction handling in fintech platforms to managing dynamic UI components in mobile apps.
In this blog, we’ll explore the two main types of polymorphism in Java: static (compile-time) and dynamic (runtime). If you’re planning to start or advance your coding career, mastering polymorphism is a must—and this guide will help you build that foundation with practical examples and clarity.
Build the future with code! Explore our diverse Software Engineering courses and kickstart your journey to becoming a tech expert.
Start Exploring Now!
Check out OOPs concepts and examples.
When something is “polymorphic,” it means it can take on many forms. In programming, polymorphism lets us treat objects as instances of their parent class instead of their specific class. This allows one interface to do different things. It makes code flexible and easier to work with.
Power your tech career with hands-on expertise in Cloud Computing, DevOps, and AI-driven Full Stack Development. Enroll in upGrad’s cutting-edge programs and shape your future in today’s evolving tech landscape.
Polymorphism in Java is one of the four main ideas in Object-Oriented Programming (OOP). There are two main types of polymorphism in Java: compile-time (also called static polymorphism) and runtime (also called dynamic polymorphism). Each type handles data and behaviors in its own way. In this section, we’ll look at both types with definitions, examples, and explanations of how polymorphism works in Java.
Compile-time Polymorphism, also known as Static Polymorphism in Java, is a type of polymorphism in Java resolved at the time of compilation. It allows methods to have the same name but perform different tasks based on parameters. Unlike dynamic polymorphism, which occurs at runtime, compile-time polymorphism in Java is determined by the compiler, making it faster and more efficient since all method calls are resolved during compilation.
Java achieves compile-time polymorphism mainly through method overloading and operator overloading to a limited extent.
1. Method Overloading(or, Function Overloading):
In method overloading, multiple methods can have the same name in a class but must differ in parameter count, parameter type, or parameter order. The Java compiler decides which method to call based on the method signature at compile time.
Example of Method Overloading:
java
class SimpleCalculator {
// Method with two parameters
int add(int a, int b) {
return a + b;
}
// Overloaded method with three parameters
int add(int a, int b, int c) {
return a + b + c;
}
}
public class Demo {
public static void main(String[] args) {
SimpleCalculator obj = new SimpleCalculator();
// Calls the method with two parameters
System.out.println(obj.add(25, 25)); // Output: 50
// Calls the method with three parameters
System.out.println(obj.add(25, 25, 30)); // Output: 80
}
}
Output
50
80
Explanation
Here, the add method is overloaded with two versions: one that accepts two parameters and another that accepts three parameters. Based on the arguments passed, the compiler determines which method to call.
2. Rules for Method Overloading in Java:
3. Limitations:
Java does not support operator overloading (except for the + operator, which is used for both numeric addition and string concatenation). This is to maintain simplicity and avoid confusion.
Also Read: Abstract Class in Java – With Examples
Function overloading (or method overloading in Java) allows multiple methods with the same name to coexist in a class, as long as they differ in their parameter list (either by number, type, or order). This enables a single function name to perform different tasks based on the type of input it receives.
Code Example
java
class Printer {
void print(String text) {
System.out.println("Printing String: " + text);
}
void print(int number) {
System.out.println("Printing Integer: " + number);
}
}
public class Demo {
public static void main(String[] args) {
Printer printer = new Printer();
printer.print("Hello World"); // Calls the method with String parameter
printer.print(100); // Calls the method with int parameter
}
}
Explanation:
Output:
mathematica
Printing String: Hello World
Printing Integer: 100
Operator overloading allows an operator to perform different operations depending on the context. While Java restricts operator overloading for simplicity, it does overload the + operator to perform both addition and string concatenation.
Code Example of + Operator Overloading
java
public class OperatorOverloadingExample {
public static void main(String[] args) {
// Addition operation
int sum = 10 + 20;
System.out.println("Sum: " + sum); // Expected output: Sum: 30
// Concatenation operation
String message = "Hello" + " World";
System.out.println("Message: " + message); // Expected output: Message: Hello World
}
}
Explanation:
The + operator behaves differently based on the types of operands. If both are numeric, it adds them. If either operand is a String, it performs concatenation, demonstrating Java’s limited form of operator overloading.
Output:
30
Hello World
Must Read: Learn Java free!
Java does not have templates like C++, but it uses generics to achieve similar functionality. Generics allow classes and methods to operate on various data types without requiring specific implementations for each type, making code more flexible and type-safe.
Code Example of Generics in Java
java
class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
public class Demo {
public static void main(String[] args) {
Box<Integer> intBox = new Box<>();
intBox.setItem(100);
System.out.println(intBox.getItem()); // Output: 100
Box<String> strBox = new Box<>();
strBox.setItem("Hello Generics");
System.out.println(strBox.getItem()); // Output: Hello Generics
}
}
Explanation:
Output:
100
Hello Generics
Runtime polymorphism, also known as dynamic polymorphism, is resolved during the program’s execution rather than at compile time. This type of polymorphism in Java is achieved through method overriding, allowing subclasses to provide specific implementations for methods already defined in their superclass. The Java Virtual Machine (JVM) determines which method to call at runtime, providing flexibility and enhancing the scalability of Java programs.
Unlike static polymorphism in Java, where the compiler knows which method to execute, in dynamic polymorphism, this decision is made by the JVM during execution. This feature enables Java programs to handle method calls flexibly, adapting to different classes and promoting code reusability.
1. Method Overriding
Example of Method Overriding:
In this example, we demonstrate runtime polymorphism using two classes, Bike and Splendor. Here, Bike is the superclass with a method run, and Splendor is a subclass that overrides the run method. By creating an instance of Splendor and assigning it to a Bike reference, we observe how Java decides which run method to execute at runtime.
java
class Bike {
void run() {
System.out.println("Bike is running");
}
}
class Splendor extends Bike {
@Override
void run() {
System.out.println("Splendor is running safely at 30 km/h");
}
public static void main(String[] args) {
Bike bike = new Splendor(); // Upcasting
bike.run(); // Output: Splendor is running safely at 30 km/h
}
}
Output:
arduino
Splendor is running safely at 30 km/h
Explanation:
This demonstrates runtime polymorphism, where the method call is resolved at runtime based on the actual type of the object (Splendor), even though the reference is of type Bike.
Learn More About: Abstract Method in Java
2. Understanding Upcasting
Example of Upcasting
In this example, we use ABC as the superclass and XYZ as the subclass. The myMethod in ABC is overridden by XYZ, and we use upcasting to assign an XYZ object to an ABC reference. This shows dynamic binding as the myMethod in XYZ is called at runtime.
java
class ABC {
public void myMethod() {
System.out.println("Method in ABC");
}
}
public class XYZ extends ABC {
@Override
public void myMethod() {
System.out.println("Method in XYZ");
}
public static void main(String[] args) {
ABC obj = new XYZ(); // Upcasting
obj.myMethod(); // Output: Method in XYZ
}
}
Output:
mathematica
Method in XYZ
Explanation:
3. Rules for Method Overriding in Java:
Java doesn’t directly use the term "virtual functions" as in C++, but the concept is similar. All non-static and non-final methods in Java are "virtual" by default, meaning they can be overridden in subclasses. When a method is called on a superclass reference pointing to a subclass object, Java dynamically selects the overridden method in the subclass at runtime. This is known as dynamic dispatch.
Polymorphism in Java is broadly classified into two types: compile-time polymorphism and runtime polymorphism. While both enable methods to behave differently based on context, they operate at distinct stages of program execution. Understanding their differences is crucial for selecting the right approach during software design and optimizing code performance.
Parameter |
Compile-Time Polymorphism (Static Binding) |
Runtime Polymorphism (Dynamic Binding) |
Definition | Polymorphism where method resolution occurs during compilation. | Polymorphism where method resolution happens at runtime based on the object instance. |
Primary Mechanism | Method Overloading (multiple methods with the same name but different parameters). | Method Overriding (subclass redefines a method from the parent class). |
Binding Time | At compile-time (early binding). | At runtime (late binding). |
Flexibility | Less flexible; behavior is fixed during compilation. | Highly flexible; behavior can adapt at runtime. |
Performance | Faster execution due to early resolution of method calls. | Slightly slower due to method lookup during execution. |
Error Detection | Errors are caught during compilation, reducing runtime failures. | Some errors may only appear during execution, requiring robust testing. |
Typical Use Cases | Utility classes, method variations based on argument types. | Implementing interfaces, abstract classes, and applying design patterns like Factory, Strategy, Observer. |
Example | void add(int a, int b) void add(double a, double b) |
class Animal { void sound() { } } class Dog extends Animal { void sound() { } } |
Both compile-time and runtime polymorphism serve distinct purposes in Java application development. Understanding where and how to apply each can significantly enhance code maintainability, flexibility, and performance. The list below outlines the practical applications for both types of polymorphism:
Compile-time polymorphism is ideal for scenarios where execution speed and resource optimization are paramount. It’s heavily used in:
Since method binding happens at compile time, these systems benefit from reduced runtime overhead.
Enterprise software demands scalability, flexibility, and maintainability. Runtime polymorphism is extensively used in:
The dynamic method binding allows businesses to adapt functionality without major code rewrites.
Popular frameworks and libraries rely on runtime polymorphism to offer extensibility and plug-in capabilities:
It enables third-party developers to extend core functionalities without altering the framework itself.
Several core design patterns are fundamentally based on runtime polymorphism, including:
These patterns promote loose coupling and dynamic behavior adjustment at runtime.
Polymorphism in Java can be categorized into compile-time polymorphism (method overloading) and runtime polymorphism (method overriding). Below are detailed explanations and examples to illustrate each type, with three examples each for compile-time and runtime polymorphism.
In compile-time polymorphism, method selection occurs at compile time based on the method signature.
In this example, the MathOperations class has an overloaded multiply method that can handle either two or three integer arguments.
java
class MathOperations {
// Method to multiply two numbers
int multiply(int a, int b) {
return a * b;
}
// Overloaded method to multiply three numbers
int multiply(int a, int b, int c) {
return a * b * c;
}
}
public class Demo {
public static void main(String[] args) {
MathOperations operations = new MathOperations();
// Calls the multiply method with two parameters
System.out.println("Multiplying two numbers: " + operations.multiply(3, 4)); // Output: 12
// Calls the multiply method with three parameters
System.out.println("Multiplying three numbers: " + operations.multiply(2, 3, 4)); // Output: 24
}
}
Explanation:
Output:
yaml
Multiplying two numbers: 12
Multiplying three numbers: 24
In this example, the DistanceCalculator class has an overloaded distance method that calculates distance based on different types of input parameters.
java
class DistanceCalculator {
// Method to calculate distance with speed and time (in km/hr and hours)
double distance(double speed, double time) {
return speed * time;
}
// Overloaded method to calculate distance using velocity and time in meters per second
double distance(int velocity, int time) {
return velocity * time;
}
}
public class Demo {
public static void main(String[] args) {
DistanceCalculator calculator = new DistanceCalculator();
// Calls the distance method with double parameters
System.out.println("Distance (km): " + calculator.distance(60.0, 2.5)); // Output: 150.0
// Calls the distance method with int parameters
System.out.println("Distance (m): " + calculator.distance(5, 10)); // Output: 50
}
}
Explanation:
Output:
scss
Distance (km): 150.0
Distance (m): 50
Here, the ShapeCalculator class overloads the calculateArea method to compute the area of different shapes based on the parameters provided.
java
class ShapeCalculator {
// Method to calculate area of a circle
double calculateArea(double radius) {
return Math.PI * radius * radius;
}
// Overloaded method to calculate area of a rectangle
double calculateArea(double length, double width) {
return length * width;
}
}
public class Demo {
public static void main(String[] args) {
ShapeCalculator calculator = new ShapeCalculator();
// Calls the calculateArea method for a circle
System.out.println("Area of circle: " + calculator.calculateArea(5.0)); // Output: 78.5398
// Calls the calculateArea method for a rectangle
System.out.println("Area of rectangle: " + calculator.calculateArea(5.0, 10.0)); // Output: 50.0
}
}
Explanation:
Output:
mathematica
Area of circle: 78.5398
Area of rectangle: 50.0
In runtime polymorphism, the method to be executed is determined during runtime based on the object type.
In this example, the Employee class has a calculatePay method that is overridden by the HourlyEmployee and SalariedEmployee subclasses to calculate pay based on different payment structures.
java
class Employee {
double calculatePay() {
return 0;
}
}
class HourlyEmployee extends Employee {
double hoursWorked;
double hourlyRate;
HourlyEmployee(double hoursWorked, double hourlyRate) {
this.hoursWorked = hoursWorked;
this.hourlyRate = hourlyRate;
}
@Override
double calculatePay() {
return hoursWorked * hourlyRate;
}
}
class SalariedEmployee extends Employee {
double monthlySalary;
SalariedEmployee(double monthlySalary) {
this.monthlySalary = monthlySalary;
}
@Override
double calculatePay() {
return monthlySalary;
}
}
public class Demo {
public static void main(String[] args) {
Employee hourly = new HourlyEmployee(40, 120); // 40 hours at ₹120/hour
Employee salaried = new SalariedEmployee(50000); // Monthly salary of ₹50,000
System.out.println("Hourly Employee Pay: ₹" + hourly.calculatePay()); // Output: ₹4800.0
System.out.println("Salaried Employee Pay: ₹" + salaried.calculatePay()); // Output: ₹50000.0
}
}
yaml
Hourly Employee Pay: ₹4800.0
Salaried Employee Pay: ₹50000.0
In this example, we have a LibraryItem superclass with a displayInfo method. Book and Magazine subclasses override this method to display information specific to each item type.
java
class LibraryItem {
void displayInfo() {
System.out.println("Library item information.");
}
}
class Book extends LibraryItem {
@Override
void displayInfo() {
System.out.println("This is a book.");
}
}
class Magazine extends LibraryItem {
@Override
void displayInfo() {
System.out.println("This is a magazine.");
}
}
public class Demo {
public static void main(String[] args) {
LibraryItem item1 = new Book();
LibraryItem item2 = new Magazine();
item1.displayInfo(); // Output: This is a book.
item2.displayInfo(); // Output: This is a magazine.
}
}
Explanation:
Output:
csharp
This is a book.
This is a magazine.
This example includes a Shape superclass with an overridden perimeter method in subclasses Rectangle and Triangle.
java
class Shape {
double perimeter() {
return 0;
}
}
class Rectangle extends Shape {
double length, width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
double perimeter() {
return 2 * (length + width);
}
}
class Triangle extends Shape {
double a, b, c;
Triangle(double a, double b, double c) {
this.a = a;
this.b = b;
this.c = c;
}
@Override
double perimeter() {
return a + b + c;
}
}
public class Demo {
public static void main(String[] args) {
Shape rectangle = new Rectangle(5, 10);
Shape triangle = new Triangle(3, 4, 5);
System.out.println("Rectangle Perimeter: " + rectangle.perimeter()); // Output: 30.0
System.out.println("Triangle Perimeter: " + triangle.perimeter()); // Output: 12.0
}
}
Explanation:
Output:
mathematica
Rectangle Perimeter: 30.0
Triangle Perimeter: 12.0
Also Read: Polymorphism In OOPS
Polymorphism in Java provides numerous benefits, making code more flexible, reusable, and easier to manage. Here’s a breakdown of key advantages:
Advantage |
Explanation |
Code Reusability |
Reduces redundancy by allowing shared methods across classes, enabling a single method to handle multiple object types. |
Flexibility and Scalability |
Allows objects to operate differently depending on the context, making the code adaptable to future changes. |
Improved Readability |
Streamlines code by using common interfaces, making it clear what methods are intended to do across different objects. |
Dynamic Binding Support |
Enables the correct method to be called at runtime based on the object’s class, making the code versatile and responsive. |
Easier Generic Programming |
Allows objects to be treated as a single type, simplifying the handling of different object types and reducing code complexity. |
While polymorphism is powerful, it does have some drawbacks that can impact performance, complexity, and debugging.
Disadvantage |
Explanation |
Increased Complexity |
Understanding polymorphic behavior can be challenging for beginners, especially in larger codebases. |
Potential for Overhead |
Dynamic method dispatch requires extra computation at runtime, which can lead to performance costs. |
Debugging Difficulties |
Polymorphic code can be harder to debug and trace, as the correct method is chosen at runtime, making it less predictable in some cases. |
Master Java with upGrad!
Learn core to advanced Java skills and start building dynamic web apps.
Ready to master JavaScript? Join upGrad’s Course Today!
Get started with the latest Java tutorials and level up your skills!
Polymorphism in Java is a fundamental concept that enables objects to take on multiple forms, enhancing code flexibility and maintainability. It manifests in two primary types: static (compile-time) polymorphism, achieved through method overloading, and dynamic (runtime) polymorphism, realized via method overriding.
Understanding and effectively implementing both forms allows developers to write more adaptable and scalable code, as it promotes the use of a unified interface for different underlying data types. By leveraging polymorphism, Java programs can handle various object types uniformly, leading to more robust and extensible software design.
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.
408 articles published
Software Engineering Manager @ upGrad. Passionate about building large scale web apps with delightful experiences. In pursuit of transforming engineers into leaders.
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