Java Method Overriding
Override parent class methods in Java subclasses, with the @Override annotation and dynamic dispatch.
Overriding is when a subclass declares a method with the same signature as one it inherited, replacing the parent's version. Combined with polymorphism, it's the mechanism that lets you call a method on a parent-typed reference and have the right subclass implementation run.
This chapter pins down the rules: what counts as an override, what doesn't, what the compiler will let you change in the subclass version, and what @Override actually buys you.
What an override looks like
A method overrides an inherited one when all of these hold:
- Same name.
- Same parameter list (types and order, after generic erasure).
- Return type same as the parent's, or a subtype of it (covariant return).
- Visibility same as the parent's or wider.
- Not declared
final,static, orprivateon the parent.
class Animal {
String speak() { return "(noise)"; }
}
class Cat extends Animal {
@Override
String speak() { return "meow"; }
}That's a valid override. Calling speak() on a Cat returns "meow"; calling it through an Animal reference that points at a Cat also returns "meow" — dispatch is decided by the actual object's class.
@Override — use it always
@Override is an annotation that tells the compiler "I intend this to override an inherited method." If it doesn't actually override one — wrong name, wrong parameters, wrong return type — the compiler errors out:
class Cat extends Animal {
@Override
String Speak() { return "meow"; } // ERROR — no Speak() in Animal
}Without the annotation, this would silently compile as a brand-new method called Speak, and ((Animal)cat).speak() would keep returning the parent's "(noise)". The annotation costs nothing and catches a whole family of silent bugs. Always write it on overrides.
Overriding vs overloading
| Aspect | Overriding | Overloading |
|---|---|---|
| Where | Across a subclass / superclass pair | In the same class |
| Signature | Must match the parent's | Must differ from the others |
| Dispatch | Runtime, based on actual object | Compile-time, based on argument types |
| Annotation | @Override | none |
These get confused all the time. Overriding is one method, several types of object. Overloading is several methods, one type, picked by what you pass in.
Covariant return types
The subclass can declare a narrower return type than the parent:
class Animal {
Animal makeBaby() { return new Animal(); }
}
class Cat extends Animal {
@Override
Cat makeBaby() { return new Cat(); } // returns Cat, not Animal — fine
}This is covariance — the override's return type is a subtype of the parent's. It's safe because any caller expecting an Animal from makeBaby() happily accepts a Cat. The benefit is that callers who know they have a Cat get a Cat back without casting:
Cat kitten = new Cat().makeBaby(); // no cast neededGoing the other way (a subclass widening the return type) is not allowed — it would break callers that expected the narrower type.
Visibility can only widen
The override must be at least as visible as the parent's method:
class A { public void hi() { } }
class B extends A { protected void hi() { } } // ERROR — reduces visibility
class C extends A { public void hi() { } } // okNarrowing would mean a caller holding an A reference could call hi() but, after the assignment A a = new B(), they couldn't anymore — which would break substitutability.
Exceptions can only shrink
If the parent method declares no checked exceptions, the override can't declare any either. If the parent throws a checked exception, the override can throw the same one or a more specific subclass of it — but not a broader one:
class A {
void run() throws IOException { ... }
}
class B extends A {
@Override void run() throws FileNotFoundException { ... } // ok — FileNotFoundException extends IOException
}
class C extends A {
@Override void run() throws Exception { ... } // ERROR — too broad
}Unchecked exceptions (subclasses of RuntimeException) are unrestricted — you can always throw them from an override.
What you cannot override
privatemethods. They aren't visible to the subclass at all, so a method by the same name in the subclass is simply a new method.finalmethods. Marked specifically to prevent overrides.staticmethods. A subclass can declare a static method with the same name and signature, but that's called method hiding, not overriding. There's no polymorphism — the JVM resolves the call based on the reference's compile-time type:
class A { static String klass() { return "A"; } }
class B extends A { static String klass() { return "B"; } }
A a = new B();
System.out.println(a.klass()); // "A" — static, not polymorphicThis is one of the few places where the rule "method dispatch picks the actual object's version" doesn't apply.
Calling the parent from the override
A common pattern is to extend rather than replace the parent's behavior with super.method(...):
class Logger {
void log(String s) { System.out.println(s); }
}
class TimestampedLogger extends Logger {
@Override
void log(String s) {
super.log("[" + System.currentTimeMillis() + "] " + s);
}
}The override decorates the parent's behavior instead of duplicating it. Covered in the super keyword chapter.
A worked example
What's next
You've now seen how subclasses replace parent methods. The next idea — abstraction — is the flip side: declaring a method that has no default body and forcing every concrete subclass to supply one. Continue to Java abstraction.
Practice
In a subclass, why is @Override worth writing on every override even though the code would compile without it?