Welcome back! In the last two lessons, we've explored two powerful, yet distinct, features of Java: method overriding and method overloading. You learned that overriding allows a subclass to provide a specific implementation for a method inherited from its parent, while overloading allows a class to have multiple methods with the same name but different parameters.
Today, we'll bring these two concepts together to directly compare and contrast them. The goal of this lesson is to solidify your understanding of when and why to use each one. This distinction is fundamental to grasping polymorphism, a cornerstone of object-oriented programming, and is a very common topic in technical interviews for Java developers. By the end of this lesson, you will be able to clearly distinguish between compile-time (overloading) and runtime (overriding) polymorphism.
Polymorphism: The Two Flavors
Polymorphism allows us to perform a single action in different ways. In Java, this is primarily achieved through method overloading and method overriding. While they both use methods with the same name, they operate on completely different principles and at different times in a program's life cycle.
Let's start with a high-level visual comparison.

This image captures the structural difference. Now, let's explore the conceptual difference, which revolves around when the decision is made about which method to call.
The following video provides an excellent explanation of both concepts using real-life analogies—animal sounds for overriding and booking movie tickets for overloading—before diving into the code. This will help build your intuition.
Polymorphism in Java Explained with Real-Life Examples | Method Overloading & Overriding Simplified
This video from CodeSnippet clearly explains both types of polymorphism with practical examples and code implementations.
Watch the following sections: Runtime Polymorphism (Overriding): The presenter uses an Animal class with Dog and Cat subclasses to demonstrate how the speak() method is resolved at runtime. Pay close attention to the terms "dynamic binding" and "late binding." Compile-time Polymorphism (Overloading): Here, a Theater class is used to show how multiple bookTickets() methods with different parameters are resolved by the compiler. Note the terms "static binding" and "early binding."
As the video highlights, the core difference is about timing:
- Method Overloading is resolved at compile time. The compiler analyzes the method call (
myObject.myMethod(arg1, arg2)) and matches it to the correct overloaded version based on the number and type of arguments. This is static polymorphism or early binding. - Method Overriding is resolved at run time. The compiler only knows that you're calling a method that exists in the parent class. The Java Virtual Machine (JVM) waits until the program is running to check the actual type of the object and then calls the appropriate version of the method. This is runtime polymorphism or dynamic binding.
A Structured Comparison
To make the distinctions crystal clear, let's look at a summary table. The following article from DigitalOcean provides a concise and accurate breakdown.
Overriding vs Overloading in Java
This article offers a direct comparison that neatly summarizes the key differences.
Please read the section Comparing overriding and overloading. Focus on the table, as it directly contrasts the two concepts across several important criteria.
Let's distill the information from that table into a few key points:
| Feature | Method Overloading | Method Overriding |
|---|---|---|
| Purpose | To add more methods with the same name but different functionality within the same class. | To provide a specific implementation of an inherited method in a subclass. |
| Relationship | Occurs within a single class. | Occurs between a superclass and subclass (inheritance). |
| Method Signature | Name is the same, but the parameter list must be different (number, type, or order of parameters). | Name and the parameter list must be the same. |
| Polymorphism Type | Compile-time (Static) Polymorphism | Runtime (Dynamic) Polymorphism |
| Binding Time | Early Binding (at compile time) | Late Binding (at run time) |
| Static Methods | Can be overloaded. | Cannot be overridden (this is called method hiding). |
| Access Modifiers | Can have any access modifier. | Cannot have a more restrictive access modifier than the parent's method. |
The "How": Dynamic Method Dispatch
You might be wondering how the JVM achieves runtime polymorphism. The mechanism behind it is called Dynamic Method Dispatch. This is the process that allows a superclass reference variable to refer to a subclass object and still invoke the subclass's overridden method.
Consider this classic example:
class Animal {
public void makeSound() {
System.out.println("Generic animal sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Woof");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Dog(); // Superclass reference, subclass object
myAnimal.makeSound(); // Which method is called?
}
}
The variable myAnimal is of type Animal, but the object it points to is of type Dog. At compile time, the compiler only checks if the Animal class has a makeSound() method. It does, so the code compiles. However, at runtime, the JVM inspects the actual object (new Dog()) and sees that it has an overridden makeSound() method. Therefore, it calls the Dog's version, and the output is "Woof". This decision-making process at runtime is Dynamic Method Dispatch.
The following video from Telusko provides a fantastic walkthrough of this exact concept.
#56 Dynamic Method Dispatch in Java
This video explains the mechanics of runtime polymorphism through Dynamic Method Dispatch.
Watch from the main explanation. The presenter does an excellent job of: Showing how a parent reference can hold a child object (A obj = new B()). Demonstrating that the overridden method in the child class is called. Using a memory diagram to illustrate how the object reference points to different objects in the heap, leading to different method calls. Emphasizing that this is only possible through inheritance.
Seeing Both in Action
To tie everything together, let's examine a single piece of code that uses both overloading and overriding. The DigitalOcean article we looked at earlier provides a great example.
Overriding vs Overloading in Java
This article contains a unified code example demonstrating both concepts.
Read the section "Overriding and overloading example". In the Processor class, notice how the process method is overloaded with different parameters ((int, int), (int[]), (Object[])). Then, see how the MathProcessor subclass overrides two of these methods to provide new functionality (calculating a sum instead of just printing).
This example clearly shows the two concepts coexisting. Overloading provides different ways to call the process method, while overriding allows a specialized subclass like MathProcessor to change what those methods actually do.
Your Turn: A Notification System
Let's solidify your understanding with a practical exercise. You'll build a simple notification system that uses both overloading and overriding.
Task:
-
Create a base class named
NotificationService.- It should have a method:
public void send(String message). This method should print[NOTIFICATION]followed by the message. - Overload this method by adding another version:
public void send(String subject, String message). This method should print[NOTIFICATION] Subject: <subject> - Body: <message>.
- It should have a method:
-
Create a subclass named
EmailServicethatextends NotificationService.- Override the
send(String message)method to print[EMAIL] Sent:followed by the message. - Override the
send(String subject, String message)method to print[EMAIL] Sent with Subject: <subject> - Body: <message>.
- Override the
-
Create another subclass named
SMSServicethatextends NotificationService.- Override the
send(String message)method to print[SMS] Delivered:followed by the message. - Override the
send(String subject, String message)method. For an SMS, a subject doesn't make much sense, so have it combine them:[SMS] Delivered: <subject> - <message>.
- Override the
-
Create a
Mainclass to test your system.- Create an
EmailServiceobject and anSMSServiceobject. - Use a
NotificationServicereference variable for each (upcasting):NotificationService emailSender = new EmailService(); NotificationService smsSender = new SMSService(); - Call both the single-parameter and two-parameter
sendmethods onemailSenderandsmsSender. - Observe how the JVM uses runtime polymorphism to call the correct overridden methods from
EmailServiceandSMSService, even though the reference type isNotificationService.
- Create an
Summary
Today we dissected the two types of polymorphism in Java, a concept crucial for building flexible and maintainable object-oriented systems.
Key Takeaways:
- Polymorphism means "many forms"; it's the ability to perform an action in different ways.
- Method Overloading is compile-time polymorphism. It occurs within a single class, using the same method name with different parameter lists. The compiler chooses the method based on the arguments.
- Method Overriding is runtime polymorphism. It occurs in a parent-child class relationship, where the child provides a specific implementation for an inherited method. The JVM chooses the method at runtime based on the actual object's type, via Dynamic Method Dispatch.
- Overloading is about providing more options or flexibility (
add(int, int),add(int, int, int)), while overriding is about changing behavior (Animal.speak()vs.Dog.speak()).
In our next lesson, we'll build directly on the idea of NotificationService sender = new EmailService();. We will formally explore upcasting, its counterpart downcasting, and how to safely check an object's type at runtime using the instanceof operator.