Java @Override Annotation
Last modified: April 13, 2025
The @Override
annotation is a marker annotation in Java that
explicitly signals a method's intention to override a method from a superclass
or interface. By using @Override
, developers ensure that the
overridden method is correctly defined, preventing accidental mismatches or
typographical errors that might otherwise go unnoticed.
One of the primary advantages of @Override
is its ability to detect
errors at compile time. If the annotated method does not successfully override a
method from the superclass or interface—due to incorrect method signatures or
missing superclass definitions—the compiler will generate an error. This
safeguard helps enforce proper inheritance practices and reduces debugging
efforts.
In addition to error prevention, @Override
enhances code
readability by making it clear which methods are intended to override parent
class implementations. This improves code maintenance, making modifications
easier for developers working on large or collaborative projects.
By consistently applying the @Override
annotation, developers write
more robust, maintainable, and error-resistant Java code while adhering to
object-oriented principles.
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
The Object
class provides fundamental methods that are inherited by
all Java objects. Commonly overridden methods such as toString
,
equals
, and hashCode
should always use the
@Override
annotation. This ensures that the overridden methods
maintain correct signatures and behavior, preventing errors caused by accidental
misdefinitions.
package com.zetcode; import java.util.Objects; 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 && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, 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 correctly overrides three essential Object
methods:
toString()
- Provides a meaningful string representation of the object for debugging and logging.equals(Object obj)
- Ensures proper equality checks by comparing object fields.hashCode()
- Generates a hash code to maintain consistency withequals()
, improving performance in hash-based collections.
Using Objects.equals(name, person.name)
instead of
name.equals(person.name)
prevents potential
NullPointerException
issues. Similarly, Objects.hash(name,
age)
simplifies hash code generation while ensuring a well-distributed
hash.
By following these best practices, developers can create reliable, efficient, and maintainable Java classes that work seamlessly with core Java frameworks and data structures.
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.