How do you author an experimental API with @RequiresOptIn, and what do its level and message parameters control?
answer
- @RequiresOptIn(message, level)
- Level.WARNING vs Level.ERROR (ERROR is default)
- Retention BINARY, restrict @Target
- Markers can't target EXPRESSION or FILE
basics
~20 sYou make your own annotation and mark it with @RequiresOptIn. Then you put that annotation on the API you want to flag. You can set a message and choose whether use is a warning or an error.
solid answer
~40 sDefine a marker annotation, set its retention to BINARY (the default historically used), and tag it with @RequiresOptIn(message = "...", level = RequiresOptIn.Level.ERROR or WARNING). Then place the marker on any class, function, property, or constructor you consider unstable. The level controls the diagnostic severity for callers who use the API without opting in: WARNING just nags, ERROR refuses to compile. The message is shown in that diagnostic. You typically restrict the annotation's targets with @Target (e.g. CLASS, FUNCTION, PROPERTY, CONSTRUCTOR, TYPEALIAS) and avoid EXPRESSION/FILE because opt-in markers can't target those. Callers then consume via @OptIn(YourMarker::class), propagate by re-annotating, or use the -opt-in flag.
code
kotlin · 10 lines@RequiresOptIn(
message = "Internal tuning API; may change without notice.",
level = RequiresOptIn.Level.ERROR
)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class ExperimentalTuning
@ExperimentalTuning
fun tune() { /* ... */ }go deeper
Knows you create an annotation and tag it with @RequiresOptIn to flag an API.
Correctly configures message, level (ERROR default), retention, and targets, and places the marker on real declarations.
Reasons about retention/target constraints and the propagation contract markers create for downstream code.
Designs a marker taxonomy for a library's stability tiers and weighs WARNING-then-ERROR migration paths.
## Authoring a marker An opt-in marker is just an annotation class that you tag with `@RequiresOptIn`: ```kotlin @RequiresOptIn( message = "This API is experimental and may change.", level = RequiresOptIn.Level.ERROR ) @Retention(AnnotationRetention.BINARY) @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) annotation class MyExperimentalApi @MyExperimentalApi fun fragileFeature() { /* ... */ } ``` ## `@RequiresOptIn` parameters - **`message`** — text surfaced in the compiler diagnostic when someone uses the API without opting in. Use it to explain *why* it's unstable. - **`level`** — a `RequiresOptIn.Level` enum: - `WARNING` — usage compiles but emits a warning. - `ERROR` (the default) — usage fails to compile until the caller opts in. ## Retention and targets - **`@Retention(AnnotationRetention.BINARY)`** is the conventional retention for markers — present in compiled bytecode but not reflectively at runtime via `SOURCE`. (`RUNTIME` is not appropriate; `SOURCE`-only would lose the info downstream.) - **`@Target`** should list the program elements the marker can annotate — typically `CLASS`, `ANNOTATION_CLASS`, `PROPERTY`, `FIELD`, `LOCAL_VARIABLE`, `VALUE_PARAMETER`, `CONSTRUCTOR`, `FUNCTION`, `PROPERTY_GETTER`, `PROPERTY_SETTER`, `TYPEALIAS`. Markers **cannot** target `EXPRESSION` or `FILE` (the compiler forbids it for marker annotations). ## How callers respond Once marked, the API can only be used by callers that: 1. add `@OptIn(MyExperimentalApi::class)` locally, or 2. re-annotate their own declaration with `@MyExperimentalApi` to propagate, or 3. pass `-opt-in=pkg.MyExperimentalApi` module-wide. ## Why this design It lets a library ship unstable APIs *without* hiding them, while forcing a conscious, greppable acknowledgement at every use site — a clean API-evolution lever.
- What is the default level if you omit it from @RequiresOptIn?ERROR. Omitting level means callers must opt in or the code won't compile. Use WARNING explicitly for a softer signal.
- Why BINARY retention rather than SOURCE?BINARY keeps the marker in compiled output so the compiler can still enforce opt-in for downstream modules consuming the library; SOURCE-only would lose that enforcement across module boundaries.
saying these in an interview costs you the question
- Saying the default level is WARNING (it is ERROR)
- Targeting EXPRESSION or FILE on a marker (forbidden)
- Using RUNTIME retention reflexively without reason
- Forgetting that @RequiresOptIn goes on the marker, not directly on the API