In Java, overloading means declaring methods with the same name but different parameter lists, while overriding means a subclass provides a new implementation of an inherited instance method with a compatible signature. Overloading is resolved mainly at compile time from the call’s declared types and arguments; overriding is selected at runtime from the actual object’s class.
What overloading and overriding have in common
Both features let programmers use a familiar method name in more than one context, but they solve different problems. Overloading gives one class several ways to perform an operation, such as accepting different kinds or numbers of inputs. Overriding lets a subtype preserve a method’s contract while changing what that method does for its own objects.
The distinction matters when reading code because the method name alone does not tell you which method runs. For an overloaded call, Java chooses among methods based on the arguments and the type information available at the call site. For an overridden method, the selected implementation depends on the runtime class of the receiver object.
How method overloading works
A class overloads a method by declaring methods with the same name but different parameter lists. A parameter list is different if it has a different number of parameters, different parameter types, or a different order of types. Return type alone does not distinguish overloads.
class Printer {
void print(String text) {
System.out.println("Text: " + text);
}
void print(int number) {
System.out.println("Number: " + number);
}
void print(String text, int copies) {
System.out.println(text + " x " + copies);
}
}
Printer printer = new Printer();
printer.print("Hello");
printer.print(7);
printer.print("Note", 2);
The output is:
Text: Hello
Number: 7
Note x 2
Each call has the same method name, but its arguments identify a suitable parameter list. The methods need not have different bodies in principle; what makes them overloads is their distinct parameter lists.
How method overriding works
Overriding occurs when a subclass declares an instance method that matches an inherited method’s signature and provides a compatible return type. The subclass implementation replaces the inherited behavior when that method is invoked on an instance of the subclass.
class Animal {
void speak() {
System.out.println("An animal sound");
}
}
class Dog extends Animal {
@Override
void speak() {
System.out.println("Bark");
}
}
Animal pet = new Dog();
pet.speak();
The output is Bark. The variable pet is declared as Animal, but it refers to a Dog object. Since speak is an overridable instance method, Java dispatches the call to the implementation belonging to the object’s runtime class.
Rules that distinguish the two
Overloading and overriding follow different rules. For a practical review, check these points:
- Parameters: Overloaded methods must have different parameter lists. An overriding method must match the inherited method’s name and parameter types.
- Return types: A different return type by itself cannot create an overload. An overriding method must return the same type or a covariant reference type, meaning a more specific subtype of the inherited return type.
- Access: An overriding method cannot be less accessible than the method it overrides. For example, a public method cannot be overridden with a protected or private method.
- Exceptions: An overriding method cannot add broader checked exceptions than the inherited method permits. It may omit them or declare compatible narrower checked exceptions; unchecked exceptions are not constrained in the same way.
- Inheritance: Overriding concerns an inherited method in a subtype. Overloading can happen within one class and does not require inheritance.
- Static methods: Static methods are hidden, not overridden. A subclass can declare a static method with the same signature, but selection is based on the reference type rather than runtime instance dispatch.
- Private methods: A private method is not inherited as an overridable method, so a same-named method in a subclass does not override it.
The @Override annotation is useful on methods intended to override a parent method or implement an interface method. If the signature does not actually match an inherited method, the compiler reports an error rather than silently treating the declaration as an unrelated method.
Compile-time selection and runtime dispatch
Overloading is resolved at compile time. The compiler considers the methods available from the compile-time type of the expression, then chooses the most specific applicable method based on the argument expressions. This is why a variable’s declared type can affect which overload is selected, even when it refers to an object of a more specific runtime class.
class Parent { }
class Child extends Parent { }
class Show {
void display(Parent value) {
System.out.println("Parent parameter");
}
void display(Child value) {
System.out.println("Child parameter");
}
}
Parent value = new Child();
new Show().display(value);
This prints Parent parameter. The argument expression is declared as Parent, so the overload taking Parent is selected. The object itself is a Child, but that runtime fact does not change overload resolution.
Overriding uses dynamic dispatch for eligible instance methods. Once the compiler has determined the method signature being called, Java selects the implementation associated with the actual receiver object. Thus, the declared type helps determine whether a call is legal, while the runtime class can determine which overridden body executes.
Overloading and overriding in the same example
A class hierarchy can use both features at once. Consider a base class with an overloaded method and a subclass that overrides one of those signatures:
class Message {
void send(String text) {
System.out.println("Message: " + text);
}
void send(String text, int priority) {
System.out.println("Priority " + priority + ": " + text);
}
}
class Alert extends Message {
@Override
void send(String text) {
System.out.println("Alert: " + text);
}
}
Message item = new Alert();
item.send("Check this");
item.send("Check this", 3);
The output is:
Alert: Check this
Priority 3: Check this
For the first call, the compiler identifies the one-argument signature, and runtime dispatch selects Alert.send(String). For the second, it identifies the two-argument signature inherited from Message; the subclass has not overridden that signature, so the inherited implementation runs. The two decisions happen at different stages.
Common mistakes and confusing cases
A frequent mistake is trying to overload a method by changing only its return type. These declarations are not a valid overload pair:
int calculate(int value) { return value; }
// String calculate(int value) { return "value"; } // Compile-time error
The call calculate(4) would have the same name and parameter list either way, so Java cannot use the expected return type to distinguish the declarations.
Another common confusion is assuming that a subclass method with the same name necessarily overrides its parent’s method. If the parameter types differ, it may instead be an overload. Use @Override to have the compiler verify the intention.
Also remember that fields and static methods do not use ordinary instance-method overriding. Fields can be hidden, and static methods can be hidden; neither is selected through runtime dispatch in the way an overridden instance method is. Constructors are not inherited or overridden, although constructors can be overloaded within a class.
Overload resolution can become difficult when arguments involve null, widening conversions, boxing, or varargs. For example, a null literal can match more than one reference-type parameter, and unrelated reference types may make a call ambiguous. When a call is ambiguous, Java reports a compile-time error; it does not choose based on which method seems more useful. A cast or a clearer variable type can make the intended overload explicit.
How to choose between them
Choose overloading when one operation naturally accepts different inputs and the method name should communicate the shared purpose. Keep overloads easy to distinguish: callers should be able to tell what each parameter form means without relying on surprising conversions.
Choose overriding when a subtype must provide behavior appropriate to its own kind while honoring the parent method’s contract. A well-designed override should remain usable wherever the parent type is expected. Avoid changing the meaning of the method so drastically that code written for the parent type no longer behaves sensibly.
- Use overloads for alternate input forms, such as a required value with an optional configuration argument.
- Use overrides for subtype-specific behavior, such as each implementation supplying its own response to a shared operation.
- Prefer distinct method names if two overloads have substantially different purposes or make calls ambiguous.
- Use
@Overridewhenever you intend to replace inherited instance-method behavior.
Frequently Asked Questions
Can a method be overloaded and overridden at the same time?
Yes. A class can declare overloaded signatures, and a subclass can override one or more inherited signatures. Each call first matches a signature using overload resolution, then an eligible instance-method call can dispatch to the subclass implementation.
Does changing the return type overload a method?
No. The parameter list must differ to create an overload. For overriding, the return type must be compatible with the inherited method; a subtype return type is allowed for reference types.
Are static methods overridden in Java?
No. A same-signature static method in a subclass hides the parent’s static method. Static method selection is based on the type used to make the call, not on the runtime class of an instance.
Why does an overloaded call use the parent type when the object is a child?
Overload resolution uses the compile-time types of the arguments and the methods visible through the receiver’s compile-time type. Runtime dispatch applies to the chosen signature when it is an overridable instance method; it does not redo overload selection.
What does the @Override annotation do?
It asks the compiler to verify that the method really overrides an inherited method or implements a method declared by an interface. It helps catch mismatched names, parameters, and other signature errors.
Quick summary
Overloading is about multiple parameter lists for one method name, usually within a class, and is resolved at compile time. Overriding is about a subclass replacing inherited instance-method behavior, with the implementation selected at runtime. Check the parameter list, inheritance relationship, and method kind—instance, static, private, or constructor—to identify which rule applies.