🪞 Reflection, Annotations & Modules · Advanced

Built-in annotations in Java

@Override, @Deprecated, @SuppressWarnings, @FunctionalInterface, @SafeVarargs.

🧩 The mysteryOne misspelled method name, speek() instead of speak(), and your dog goes silent forever. One annotation would have caught it before the code ever ran.

Notes for the compiler

Java ships annotations that javac understands: **@Override (verify it really overrides), @Deprecated (warn users of an API), @SuppressWarnings (silence named warnings), @FunctionalInterface (exactly one abstract method) and @SafeVarargs** (generic varargs used safely).

🔮 Predict it

A typo meets @Override

What happens?

class Shape {
    double area() { return 0; }
}
class Sq extends Shape {
    @Override
    double Area() { return 4; }
}
void main() {
    System.out.println(new Sq().area());
}
  1. Prints 4.0
  2. Prints 0.0
  3. Compile error
Show the answer

Area() (capital A) overrides nothing in Shape, so **@Override makes javac reject the code**. Without the annotation it would compile, and area() would silently return 0.0: a new method hiding the bug.

@Deprecated and @SuppressWarnings

@Deprecated(since = "9", forRemoval = true) warns every caller and states the plan. It's kept at runtime, so tools can see it. @SuppressWarnings("unchecked") silences one named warning: keep it on the smallest possible scope.

🔮 Predict it

Which ones survive?

What does this print?

class Api {
    @Deprecated @SuppressWarnings("all")
    public void old() { }
}
void main() throws Exception {
    var m = Api.class.getMethod("old");
    int n = m.getAnnotations().length;
    System.out.println(n);
}
  1. 0
  2. 1
  3. 2
Show the answer

@Deprecated has RUNTIME retention, so reflection sees it. @SuppressWarnings (like @Override) has SOURCE retention: javac uses it, then throws it away.

Counting abstract methods

@FunctionalInterface demands exactly one abstract method. Default and static methods don't count, and neither do abstract methods matching **public Object methods** like equals. This compiles: test is the only one.

@FunctionalInterface
interface Check {
    boolean test(String s);
    boolean equals(Object o); // not counted
    default Check negate() {  // not counted
        return s -> !test(s);
    }
}
⚠️ The trap

@SafeVarargs on an overridable method

@SafeVarargs is a promise, so it's allowed only where nobody can break it with an override: static, final or private methods and constructors. On an ordinary instance method it's a compile error.

class Box {
    @SafeVarargs // compile error:
    <T> void addAll(T... items) { }
}   // method can be overridden
💼 In the real world

The equals(Foo) bug

A classic: writing public boolean equals(Person p) instead of equals(Object o). It overloads instead of overriding, so HashSet never calls it. With @Override, javac flags it instantly. Teams treat a missing @Override as a code-review smell.

Key takeaways

  1. @Override turns signature typos into compile errors
  2. @Deprecated(since = "9", forRemoval = true) is visible at runtime
  3. @FunctionalInterface: exactly one abstract method
  4. @SafeVarargs only on static, final, private methods or constructors
🤯 Did you know?

@FunctionalInterface is optional: any interface with exactly one abstract method already works with lambdas. The annotation just makes the compiler enforce it.

Practice questions

What does this print?

class Animal {
    void speak() { }
}
class Dog extends Animal {
    @Override
    void speek() { }
}
void main() {
    System.out.println("ok");
}
  1. Compile error
  2. Prints a warning, then ok
  3. ok
Check your answer

Compile error. speek() doesn't override anything in Animal, so @Override makes javac reject the code.

What does this print?

class Old {
    @Override @Deprecated
    public String toString() { return "old"; }
}
void main() throws Exception {
    var m = Old.class.getMethod("toString");
    for (var a : m.getAnnotations())
        System.out.println(
            a.annotationType().getSimpleName());
}
  1. Deprecated
  2. Override
  3. Deprecated Override
  4. Override Deprecated
Check your answer

Deprecated. @Deprecated has RUNTIME retention, so reflection can see it. @Override has SOURCE retention: javac checks it and then throws it away.

The JDK's annotations are just the start. Next: design your own annotation, and decide how long it survives.