Java @Override Annotation
Last modified: April 13, 2025
The @Override
annotation is a marker annotation that indicates a
method is intended to override a method in a superclass. It helps catch errors
at compile time when the method doesn't actually override anything.
Using @Override
improves code readability and helps prevent common
mistakes in method overriding. The compiler will generate an error if the
annotated method doesn't override a method from a superclass or interface.
Basic Usage of @Override
The simplest use of @Override
is when overriding methods from a
parent class. The annotation goes immediately before the method declaration.
This ensures you're actually overriding a method as intended.
package com.zetcode; class Animal { public void makeSound() { System.out.println("Animal makes a sound"); } } class Dog extends Animal { @Override public void makeSound() { System.out.println("Dog barks"); } } public class Main { public static void main(String[] args) { Animal myDog = new Dog(); myDog.makeSound(); // Outputs: Dog barks } }
In this example, the Dog
class overrides the makeSound
method from Animal
. The @Override
annotation confirms
this is an intentional override. If we misspelled the method name, the compiler
would catch it.
Interface Implementation with @Override
@Override
can also be used when implementing interface methods.
This helps ensure you're correctly implementing all required interface methods.
The compiler will verify the method signatures match.
package com.zetcode; interface Shape { double calculateArea(); String getName(); } class Circle implements Shape { private double radius; public Circle(double radius) { this.radius = radius; } @Override public double calculateArea() { return Math.PI * radius * radius; } @Override public String getName() { return "Circle"; } } public class Main { public static void main(String[] args) { Shape circle = new Circle(5.0); System.out.println(circle.getName() + " area: " + circle.calculateArea()); } }
Here, Circle
implements the Shape
interface. Both
interface methods are marked with @Override
. If we changed a method
signature, the compiler would flag it as not matching the interface.
Detecting Mistakes with @Override
One key benefit of @Override
is catching mistakes early. If you
think you're overriding a method but actually aren't, the compiler will warn
you. This prevents subtle bugs from creeping into your code.
package com.zetcode; class Vehicle { public void startEngine() { System.out.println("Engine started"); } } class Car extends Vehicle { @Override public void startEngin() { // Misspelled method name System.out.println("Car engine started"); } } public class Main { public static void main(String[] args) { Vehicle myCar = new Car(); myCar.startEngine(); // Calls Vehicle's method } }
In this example, we intended to override startEngine
but misspelled
it as startEngin
. The @Override
annotation causes the
compiler to flag this as an error. Without it, the code would compile but behave
unexpectedly.
Overriding Object Class Methods
Common methods from Object
like toString
,
equals
, and hashCode
should always use
@Override
. This ensures proper overriding of these fundamental
methods.
package com.zetcode; class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Person person = (Person) obj; return age == person.age && name.equals(person.name); } @Override public int hashCode() { return 31 * name.hashCode() + age; } @Override public String toString() { return "Person{name='" + name + "', age=" + age + "}"; } } public class Main { public static void main(String[] args) { Person p1 = new Person("Alice", 30); Person p2 = new Person("Alice", 30); System.out.println(p1); System.out.println("p1 equals p2: " + p1.equals(p2)); } }
This example shows proper overriding of three key Object
methods.
Each uses @Override
to ensure correct signatures. The
equals
method demonstrates proper type checking and field
comparison.
Abstract Class Method Overriding
When extending abstract classes, @Override
should be used for all
concrete implementations of abstract methods. This clarifies the code's intent
and helps catch signature mismatches.
package com.zetcode; abstract class Shape { public abstract double calculateArea(); public abstract void draw(); public void describe() { System.out.println("This is a shape"); } } class Circle extends Shape { private double radius; public Circle(double radius) { this.radius = radius; } @Override public double calculateArea() { return Math.PI * radius * radius; } @Override public void draw() { System.out.println("Drawing a circle with radius " + radius); } @Override public void describe() { System.out.println("This is a circle with radius " + radius); } } public class Main { public static void main(String[] args) { Shape circle = new Circle(5.0); circle.draw(); System.out.println("Area: " + circle.calculateArea()); circle.describe(); } }
Here, Circle
implements both abstract methods and overrides
a concrete method from Shape
. All overrides are marked
with @Override
. The compiler ensures each method correctly matches
its parent declaration.
Overriding Methods from Generic Classes
When working with generic classes, @Override
helps ensure type
safety in method overrides. The annotation verifies method signatures including
generic type parameters.
package com.zetcode; class Box<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; } } class StringBox extends Box<String> { @Override public void setContent(String content) { if (content == null || content.isEmpty()) { throw new IllegalArgumentException("Content cannot be empty"); } super.setContent(content); } @Override public String getContent() { String content = super.getContent(); return content != null ? content.toUpperCase() : null; } } public class Main { public static void main(String[] args) { StringBox box = new StringBox(); box.setContent("hello"); System.out.println(box.getContent()); // Outputs: HELLO } }
In this example, StringBox
extends Box<String>
and overrides its methods. The @Override
annotations confirm we're
correctly overriding the generic methods with specific String implementations.
The compiler checks type compatibility.
Default Interface Methods and @Override
Java 8 introduced default methods in interfaces. When overriding these default
methods, @Override
should be used to make the intention clear and
catch potential errors.
package com.zetcode; interface Logger { default void log(String message) { System.out.println("Default log: " + message); } void error(String message); } class FileLogger implements Logger { @Override public void log(String message) { System.out.println("File log: " + message); } @Override public void error(String message) { System.out.println("File error: " + message.toUpperCase()); } } public class Main { public static void main(String[] args) { Logger logger = new FileLogger(); logger.log("test message"); logger.error("something went wrong"); } }
This example shows a FileLogger
that overrides both a default
method (log
) and an abstract method (error
) from the
Logger
interface. The @Override
annotations make it
clear which methods are being overridden and ensure correct signatures.
Source
Java @Override Annotation Documentation
This tutorial has covered all key aspects of the @Override
annotation with practical examples. Proper use of @Override
makes
your code more robust, readable, and maintainable by catching errors early.
Author
List all Java tutorials.