Why use of the @Override annotation is essential in Java

Java logo Back in the day, Java 1.5 introduced the notion of annotations and, of particular importance in the context of this article, the @Override annotation. @Override indicates simply that a method in a particular class overrides a method in its parent class.

Why should we need this? Surely it is obvious when one method overrides another? If a method has the same name and arguments as a corresponding method in the base class, then the base class method is overridden, right?

Well, not always. Consider the following code snippet, which shows three Java classes in the same package.
package mypackage;
public class Parent 
  {
  void doSomething()
    {
    System.out.println ("This is the parent");
    }
  } 


package mypackage;
public class Child extends Parent 
  {
  void doSomething()
    {
    System.out.println ("This is the child");
    }
  }
    
package mypackage;
class Test 
{ 
  public static void main (String[] args)
    {
    Parent p = (Parent) new Child();
    p.doSomething();
    }
} 
Executing the Test class displays the message This is the child, even though the doSomething() method call is on a reference to an instance of Parent. This, of course, is polymorphism at work -- since the variable p refers to an instance which is known at run-time to be of class Child, and there is a method Child.doSomething() that overrides Parent.doSomething(), then the method in Child is called, quite properly.

All well and good. But suppose that, during a code refactoring exercise, we move the class Child into a different package. So the definition of Child becomes:
package mypackage2;
import mypackage.parent;
public class Child extends Parent 
  {
  void doSomething()
    {
    System.out.println ("This is the child");
    }
  }
We'll also have to change the invocation in class Test, so now we have:
  Parent p = (Parent) new mypackage2.Child();
Now when we run the Test class, we get the output "This is the parent."

It seems that either run-time linking has failed, or that the doSomething() method in Child no longer overrides the corresponding method in its base class. In fact, the latter explanation is the correct one -- and it's potentially quite dangerous that a trivial change of packaging can disturb the program logic so effectively.

What's happened here is that the method doSomething() in Parent is not tagged with a public or protected modifier. Consequently, to a class outside the package mypackage, the method is effectively private. In Java, it is not an error to define a method that has the same name as a private method in a superclass -- but it doesn't create an override, either.

In practice, on many occasions where class A extends class B, A and B are going to be in the same package. In such cases, if you don't declare the method public or protected that won't prevent the method being overridden by a subclass. But this -- leaving the method with default access specification -- is a dangerous practice, because it opens the way to problems of the kind described here. A potential solution is to routinely mark all methods as protected, other than those which must genuinely remain private. This is pretty common practice in C++ programming, but there is a subtle difference between what 'protected' means in this two languages.

In C++, protected means 'accessible to subclasses only'. Java has no real equivalent of this access level -- in Java protected means 'accessible to subclasses and the package'. There is no way in Java to stipulate that a method can be overridden by subclasses, but not called by other classes in the same package. Whatever the thinking, I've noticed that Java programmers are reluctant to use 'protected' access, and the risk of silently breaking your application by a trivial packaging change is a very real one.

This is where @Override comes in. If a method is annotated in this way, it does not change the program logic at all, but it causes the compiler to emit an error if the method in question does not override a method in a base class. In this example above, if I define Child.doSomething() like this:
 @Override void doSomething() {...}
Then the compiler will fail, with the following message:
Child.java:5: error: method does not override or implement a method from a 
supertype
  @Override void doSomething()
Interestingly, I can't fix the error within Child.java, because that's not where the problem is (in this case). I need to sort out the access specification in Parent.java.

@Override is also useful for trapping trivial programmer errors, like spelling wrongly the name of the overridden method, or providing the wrong argument list.

In my view, methods in Java classes should be defined as protected or private in all cases, and never left with the default (package-private) access specification. However, since we have to use libraries we have no control over, the next best thing is to use @Override in all cases where a method is expected to override a base class method.