skip to content

How do default values work for annotation elements, and when can you omit an element when applying the annotation?

level: middleimportance: should knowfreq 45%

answer

  1. default keyword after the element
  2. no default = mandatory at use site
  3. null is never a valid default
  4. empty array/string as 'unset' sentinel
  5. marker = zero elements

basics

~20 s

Add 'default <value>' after an element to give it a default. Then you can leave that element out when using the annotation and it takes the default. Elements without a default must always be supplied.

solid answer

~40 s

You give an element a default with the default keyword: int level() default 1;. The default must be a compile-time constant of the element's type (an empty array {} is a valid default). When you apply the annotation, any element that has a default can be omitted and takes its default value; any element without a default is mandatory and must be specified, or the code won't compile. This is how an annotation can have many tunable knobs but stay terse at the call site, you only override what you need. Note null is never a valid default, so frameworks use sentinels: an empty array, an empty string, or a dedicated enum/marker constant to mean 'unset'. A 'marker' annotation simply has zero elements, so there's nothing to default or supply.

go deeper

for a junior

Knows the default keyword makes an element optional and that elements without one must be supplied.

for a middle

Explains the compile-time-constant rule for defaults, the no-null constraint, and the array/empty-array default pattern.

for a senior

Discusses sentinel-default design (empty array/string, marker classes) to model 'unset', and how defaults enable terse call sites and the value shorthand.

for a principal

Reasons about API evolution, adding an element with a default is source-compatible; choosing sentinels and defaults to keep an annotation forward/backward compatible across framework versions.

## Declaring a default After an element's declaration you can add the `default` keyword followed by a value: ```java public @interface Retry { int attempts() default 3; long backoffMs() default 100; Class<? extends Throwable>[] on() default {Exception.class}; } ``` Now `@Retry` alone means `attempts=3, backoffMs=100, on={Exception.class}`. You override only what you need: `@Retry(attempts = 5)`. ## Rules for the default value - It must be a **compile-time constant** assignable to the element's type (same restriction as any annotation value). - For an **array** element, the default is an array literal, often empty: `String[] tags() default {};`. - **`null` is never allowed** as a default (or as any value). There is no way to express 'absent' with null. - A default cannot be a runtime-computed expression; it must be a literal, a constant expression, a `Foo.class` literal, an enum constant, or a nested annotation literal. ## Mandatory vs optional elements - An element **with** a default is **optional** at the use site, you may omit it. - An element **without** a default is **mandatory**, you must supply it, or the compiler errors with something like 'missing value for element'. This is the central ergonomics lever: declare sensible defaults so the common case is `@Anno` or `@Anno("x")`, and only complex uses spell out every element. ## The 'unset' problem and sentinels Because `null` is illegal, frameworks invent **sentinel** defaults to mean 'not configured': - An **empty array** `{}` (very common). - An **empty string** `""`. - A dedicated **enum constant** like `Order.DEFAULT` or a marker `Class` such as `Void.class` / a `@interface`-local `Default.class`. The framework then treats the sentinel as 'fall back to global config'. ## Marker annotations A **marker annotation** declares **no elements at all**: ```java public @interface Beta {} ``` There is nothing to default and nothing to supply, its mere presence is the signal (like `@Override`). Applied simply as `@Beta`. ## The special `value` interplay If an element is named `value` and is the *only* element you must supply (others having defaults), you can use the shorthand `@Anno("x")` instead of `@Anno(value = "x")`. Defaults are what make this work cleanly: every *other* element needs a default so that supplying just `value` compiles. ## Pitfalls - Forgetting a default on a non-`value` element while expecting `@Anno("x")` shorthand to work → compile error, because the other element is now mandatory. - Trying `default null` → illegal. - Expecting a default array to be mutable/shared, each read of an array element returns a fresh clone.

  • How do frameworks express an 'unset' element if null isn't allowed?
    With a sentinel default, an empty array {}, an empty string "", or a dedicated enum/marker class constant, that the framework interprets as 'use the default/global value'.
  • What is a marker annotation?
    An annotation with no elements at all (e.g. @Override). Its presence alone carries meaning; there's nothing to default or supply.

saying these in an interview costs you the question

  • Writing default null
  • Assuming every element is optional
  • Thinking a default can be a runtime-computed value
  • Believing a marker annotation needs an empty value element

context