skip to content

How does @Repeatable work, and what is the role of the container annotation?

level: seniorimportance: should knowfreq 40%

answer

  1. @Repeatable -> same annotation multiple times (Java 8)
  2. Need a container with value() returning an array
  3. Compiler auto-wraps repeats into the container
  4. Read with getAnnotationsByType (container-aware)
  5. getAnnotation returns null when repeated/wrapped

basics

~10 s

@Repeatable (Java 8) lets you put the same annotation on one element multiple times. You also declare a 'container' annotation that holds an array of them; the compiler bundles the repeats into that container.

solid answer

~40 s

Before Java 8 you could not place the same annotation twice on one element. @Repeatable, a Java 8 meta-annotation, removes that limit. You mark your annotation @Repeatable(Container.class), and you separately define the container annotation — one whose single value() method is an array of your annotation type. When you write the repeated annotation multiple times, the compiler automatically wraps them into one instance of the container. At runtime, getAnnotationsByType(MyAnno.class) transparently returns all repeats whether they were written individually or via the container; getAnnotation(MyAnno.class) returns null when there are multiple (because what is actually present is the container). The container must usually share at least the retention and applicable targets of the repeatable annotation. This is purely syntactic sugar over the old container pattern, just with compiler and reflection support.

code

java · 14 lines
java
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Schedules.class)
@interface Schedule { String day(); }

@Retention(RetentionPolicy.RUNTIME)
@interface Schedules { Schedule[] value(); }

class Job {
    @Schedule(day="MON") @Schedule(day="FRI") void run() {}
}
// Job.class.getMethod("run").getAnnotationsByType(Schedule.class).length == 2
// Job.class.getMethod("run").getAnnotation(Schedule.class) == null (wrapped in Schedules)

go deeper

for a junior

Knows @Repeatable lets the same annotation appear multiple times on one element.

for a middle

Knows you also need a container annotation and that it was added in Java 8.

for a senior

Can write both annotations correctly, knows the compiler auto-wraps and that getAnnotationsByType (not getAnnotation) reads repeats.

for a principal

Designs repeatable annotation APIs with correct container retention/target constraints and weighs them against alternatives (array-valued single annotation).

## The problem it solves An **annotation** is an `@`-marker on code. Before Java 8, the language forbade applying the *same* annotation type more than once to a single element: `@Schedule(...) @Schedule(...) void run()` was a compile error. People worked around it with a hand-written 'container' annotation holding an array, e.g. `@Schedules({@Schedule(...), @Schedule(...)})`. ## What @Repeatable does `@Repeatable` (a Java 8 meta-annotation) makes the *natural* repeated syntax legal while keeping the container under the hood. Two declarations are required: 1. The **repeatable annotation**, marked `@Repeatable(ContainerType.class)`. 2. The **container annotation**, which must declare a single element named `value()` whose type is an *array* of the repeatable annotation. ```java import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Repeatable(Schedules.class) @interface Schedule { String day(); } @Retention(RetentionPolicy.RUNTIME) @interface Schedules { Schedule[] value(); } // the container ``` Now you can write: ```java @Schedule(day="MON") @Schedule(day="FRI") void job() {} ``` ## What the compiler actually does When it sees two or more `@Schedule`, the compiler *implicitly* wraps them into a single `@Schedules` container holding the array. So at the bytecode level there is one `@Schedules`, not two `@Schedule`s. This is why it is called syntactic sugar. ## Reading them at runtime — the gotcha - `element.getAnnotationsByType(Schedule.class)` is **container-aware**: it returns all `Schedule` instances whether they were written singly or wrapped in the container. **Use this.** - `element.getAnnotation(Schedule.class)` returns `null` when the repeats were bundled, because what is physically present is `@Schedules`, not a lone `@Schedule`. (If exactly one was written, no container is created and getAnnotation works.) - `getDeclaredAnnotationsByType(...)` is the declared-only counterpart. ## Rules the container must satisfy - Its `value()` element is an array of the repeatable type. - Its retention must be **at least as long** as the repeatable's (the JDK requires it not be more restrictive), and its `@Target` set must be compatible, otherwise you get a compile error. Practically, give them matching `@Retention` and `@Target`. ## Why it matters It lets APIs accept naturally repeated metadata (multiple roles, multiple schedules, multiple constraints) without forcing users to know about the container, while reflection (`getAnnotationsByType`) hides the wrapping. The main bug source is reaching for `getAnnotation` and getting `null` for repeated annotations.

  • Why does getAnnotation(Schedule.class) return null when @Schedule is applied twice?
    Because the compiler bundled the two @Schedule uses into one @Schedules container, so the element physically carries @Schedules, not a standalone @Schedule. Use getAnnotationsByType(Schedule.class), which is container-aware, to retrieve all of them.
  • What must the container annotation's value() method look like?
    It must be named value() and return an array of the repeatable annotation type, e.g. Schedule[] value(). The container's retention and target must also be compatible (at least as broad) with the repeatable annotation.

saying these in an interview costs you the question

  • Thinking @Repeatable alone is enough — you must also declare the container annotation.
  • Using getAnnotation instead of getAnnotationsByType to read repeated annotations.
  • Claiming repeated annotations were possible before Java 8.
  • Naming the container's element something other than value() or not returning an array.

context