W3docs

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, or private on 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

AspectOverridingOverloading
WhereAcross a subclass / superclass pairIn the same class
SignatureMust match the parent'sMust differ from the others
DispatchRuntime, based on actual objectCompile-time, based on argument types
Annotation@Overridenone

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 needed

Going 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() { } }   // ok

Narrowing 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

  • private methods. They aren't visible to the subclass at all, so a method by the same name in the subclass is simply a new method.
  • final methods. Marked specifically to prevent overrides.
  • static methods. 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 polymorphic

This 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

java— editable, runs on the server

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

Practice

In a subclass, why is @Override worth writing on every override even though the code would compile without it?