Java Built-in Annotations
Common built-in Java annotations — @Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface.
Java Built-in Annotations
The standard library ships a small set of annotations in java.lang that the compiler treats specially. None of them adds behaviour to your code at runtime; they're all hints to javac (or, for @Deprecated, to the toolchain at large). Knowing what each one promises — and what it doesn't — is the practical part of working with annotations day to day.
The five you'll see most often:
@Override— "this method overrides one from a supertype."@Deprecated— "don't use this; it's going away."@SuppressWarnings— "silence these specific warnings in this scope."@SafeVarargs— "my varargs method doesn't pollute the heap."@FunctionalInterface— "this interface has exactly one abstract method."
@Override
Putting @Override on a method tells the compiler the method is intended to override a superclass method or implement an interface method. If it doesn't actually override anything (because the name is misspelled, the signature is wrong, or the parent removed the method), javac fails the build.
class Animal {
public String speak() { return "?"; }
}
class Dog extends Animal {
@Override
public String speak() { return "woof"; } // OK
@Override
public String spaek() { return "oops"; } // compile error: nothing to override
}It catches the kind of bug that's very hard to find at runtime: an overriding method whose signature drifts away from the superclass. Always write @Override on methods you intend to override. It has SOURCE retention, so it's gone the moment compilation finishes.
@Deprecated
@Deprecated marks something — class, method, field, constructor — as discouraged. The compiler warns at every use site. Since Java 9 the annotation takes two elements:
@Deprecated(since = "1.4", forRemoval = true)
public void oldApi() { ... }sincedocuments when the deprecation started.forRemoval = trueis a stronger signal: a future release plans to delete the API. The compiler emits a removal warning (typically louder than a plain deprecation warning) and the Javadoc tool flags it differently.
Unlike @Override, @Deprecated has RUNTIME retention — bytecode tools, IDEs, and reflection can all see it. The companion Javadoc tag @deprecated (lowercase, in a doc comment) carries the explanation; the annotation triggers the tooling.
@SuppressWarnings
When the compiler is right almost always but wrong here, @SuppressWarnings silences a category of warnings inside the annotated element:
@SuppressWarnings("unchecked")
List<String> strings = (List<String>) raw; // raw cast intentional
@SuppressWarnings({"unchecked", "rawtypes"})
public void uglyButNeeded() { ... }The string element names a warning category; the common ones are unchecked, rawtypes, deprecation, serial, unused, removal. Compilers may accept others. Two rules to keep this clean:
- Annotate the smallest possible scope. Suppress on a local variable or a single method, never on a class, unless every line really needs it.
- Pair it with a comment explaining why. A bare
@SuppressWarnings("unchecked")next to a cast leaves the next reader wondering whether the cast is actually safe.
@SafeVarargs
A varargs method whose parameter type contains a type parameter has a subtle problem: at the call site, the compiler may have to create an array of a generic type, which is unsafe. The compiler warns about this with the "possible heap pollution" message. If the author has verified that the body doesn't leak the array or write the wrong types into it, @SafeVarargs silences the warning:
@SafeVarargs
public final <T> List<T> listOf(T... items) {
return java.util.List.of(items); // only reads the items, never stores other types
}Rules:
- Only legal on methods that can't be overridden —
static,final, orprivate, plus record-style constructors. - The annotation is a promise. If your body actually does write into the varargs array with a wrong type, the cast at the call site can fail later with a confusing
ClassCastException.
It's SOURCE retention.
@FunctionalInterface
A functional interface is one with exactly one abstract method — the shape Runnable, Callable, Comparator, and Function all share. Lambda expressions and method references target functional interfaces. The annotation makes the intent explicit and asks the compiler to enforce the single-abstract-method rule:
@FunctionalInterface
public interface StringMapper {
String map(String input); // the single abstract method
default StringMapper andThen(StringMapper next) { // default methods are allowed
return s -> next.map(map(s));
}
}If you later add a second abstract method, the build breaks immediately. Without the annotation the interface would silently stop being usable as a lambda target — which a user would only notice at the call site.
Like @Override, it has SOURCE retention.
A worked example: seeing the compiler enforcement
This program demonstrates the four enforced annotations and shows what the runtime can see afterward. The interesting parts: the @SafeVarargs method actually compiles where the unannotated one prints a warning; the @FunctionalInterface is reflected via the SAM-counting rules; the @Deprecated element values come through at runtime.
What to take from the run:
@FunctionalInterfacedid its compile-time job by guaranteeingStringMapperis a SAM type:String::toUpperCaseand thes -> s + \"!\"lambda both bound to it cleanly. If somebody added a second abstract method to the interface, the compile would fail and these expressions would stop resolving.- The
@Overrideline was the bookkeeping that ensuredChild.describe()actually overridesParent.describe(). The polymorphic call landing on\"child\"confirms it; if the signature had drifted (different name, different return type), the build would have failed instead of producing wrong runtime behaviour. @Deprecatedis the one annotation here that survives compilation. Reflection successfully pulledsince=1.4andforRemoval=trueout of the class file. The method itself still ran —@Deprecatedwarns, it doesn't disable.@SafeVarargsremoved the compile-time "possible heap pollution" warning while keeping the type-safe call. Note the method isstatic, so it satisfies the "cannot be overridden" rule. Removing the annotation would compile but yell duringjavac; adding it on a non-static, non-final, non-private method would be a compile error.@SuppressWarningsleft no runtime trace — the printed annotation array forparseOrZerois empty. That's the entire purpose ofSOURCEretention: the annotation does its work during compilation and then disappears, keeping the class file uncluttered.
Other built-ins worth knowing
A handful of less-common annotations from the standard library, briefly:
@SuppressWarnings("preview")— for code using preview language features (Java 14+).@Native(java.lang.annotation.Native) — marks a constant that may be referenced from native code; used by tools that generate JNI headers.@Generated(javax.annotation.processing.Generated, Java 9+) — added by code generators on files they emit.@Documented,@Retention,@Target,@Inherited,@Repeatable— these are meta-annotations; covered in the next chapter.
You'll rarely write the first three by hand. The meta-annotations are the gateway to writing your own annotation types.
Practice
A method is declared `public <T> T[] toArray(T... values)` and the compiler warns about 'possible heap pollution from parameterized vararg type'. The author looks at the body, confirms it only writes elements of type T into the array, and adds `@SafeVarargs`. Why does the compiler reject the annotation?