# Elide annotations

> Every annotation in `dev.elide.annotations` (`dev.elide:elide-api`) for Java and Kotlin programs: targets, retention, what each one does in Elide, and how to add the package to a project.

`dev.elide.annotations` ships in the bundled `dev.elide:elide-api` 1.0.0 JAR. It is not published to Maven Central.
Two annotations change what Elide does: `@Delicate` (Kotlin opt-in) and `@Generated` (coverage). The others are
markers that a program or library can read; Elide does not act on them.

| Annotation | Kotlin targets | Java targets | Retention | Effect in Elide |
| --- | --- | --- | --- | --- |
| [`@API`](#api) | class, type | type, type use | Runtime | None |
| [`@Delicate`](#delicate) | class, property, field, local variable, value parameter, constructor, function, getter, setter, typealias | type, field, method, parameter, constructor, local variable | Runtime | Kotlin callers must opt in |
| [`@Eager`](#eager) | class, annotation class | type, annotation type | Class file | None |
| [`@Factory`](#factory) | class | type | Runtime | None |
| [`@Generated`](#generated) | class, function, file, type | type, method, type use | Class file | Left out of JVM coverage reports |
| [`@Inject`](#inject-and-singleton) | Alias of `jakarta.inject.Inject` | Not available | Runtime | None |
| [`@Nonnull`](#nonnull-and-nullable) | class, type | type, type use | Runtime | None |
| [`@Nullable`](#nonnull-and-nullable) | class, type | type, type use | Runtime | None |
| [`@Secret`](#secret) | property, class | type | Runtime | None |
| [`@Singleton`](#inject-and-singleton) | Alias of `jakarta.inject.Singleton` | Not available | Runtime | None |

None of them take parameters. "Class file" retention means the annotation is in the bytecode but not visible to
reflection.

## Add to a project

| Program | Elide annotations |
| --- | --- |
| Java file run with `elide run` | On the classpath |
| Kotlin file run with `elide run` | On the classpath |
| Kotlin source set | On the compile and run classpaths |
| Java source set | Declare it: |

```pkl
dependencies {
  maven {
    compileOnly { "dev.elide:elide-api:1.0.0" }
  }
}
```

The annotations are compiled from Kotlin, so Elide puts the Kotlin standard library on the Java compile classpath
alongside `elide-api`; Java compiles emit no `unknown enum constant` warnings.

## @API

Marks an interface or abstract class as the API surface of a unit of logic. Readable by reflection.

```java
@API
interface Greeter {
  String greet(String name);
}
```

```kotlin
@API
interface Greeter {
  fun greet(name: String): String
}
```

## @Delicate

Marks an API that callers should use with care. In Kotlin it is a `@RequiresOptIn` marker: a call without
`@OptIn(Delicate::class)` fails to compile.

```kotlin
@Delicate
fun reset() {}

@OptIn(Delicate::class)
fun main() {
  reset()
}
```

Without the `@OptIn`:

```text
error: kotlinc: This API is marked delicate; opt in with @OptIn(Delicate::class) if you understand its caveats.
```

Java has no opt-in check, so Java callers compile without it:

```java
@Delicate
static void reset() {}
```

## @Eager

Marks a class for eager initialization. Class-file retention only; Elide does not act on it.

```java
@Eager
static class Startup {}
```

```kotlin
@Eager
class Startup
```

## @Factory

Marks a class that creates instances of another type. Readable by reflection.

```java
@Factory
static class GreeterFactory {
  static Greeter create() {
    return name -> "Hello, " + name;
  }
}
```

```kotlin
@Factory
object GreeterFactory {
  fun create(): Greeter = Polite()
}
```

## @Generated

Marks generated code. JVM coverage reports from `elide test --coverage` and `elide run --coverage` leave out annotated
classes, methods and Kotlin files ([Coverage](https://elide.help/docs/toolchain/testing#coverage)).

```java
@Generated
static String banner() {
  return "generated";
}
```

```kotlin
@Generated
fun banner() = "generated"
```

A whole Kotlin file:

```kotlin
@file:Generated

import dev.elide.annotations.Generated

fun mapAll(xs: List<Int>) = xs.map { it * 2 }
```

## @Inject and @Singleton

Kotlin type aliases of `jakarta.inject.Inject` and `jakarta.inject.Singleton`. They need
`jakarta.inject:jakarta.inject-api` on the classpath, which Elide does not add. Elide has no dependency injection
container that reads them; for compile-time injection in Kotlin, use [Metro](https://elide.help/docs/api-jvm-libraries#metro). Java code
cannot import them; use `jakarta.inject` directly.

```pkl
dependencies {
  maven {
    packages { "jakarta.inject:jakarta.inject-api:2.0.1" }
  }
}
```

```kotlin
@Singleton
class Polite @Inject constructor() : Greeter {
  override fun greet(name: String) = "Hello, $name"
}
```

## @Nonnull and @Nullable

Type-use annotations for documenting nullability. Neither compiler checks them, and Kotlin does not read them as
nullability: a Java method returning `@Nullable String` is still a platform type in Kotlin.

```java
static @Nullable String find(@Nonnull String id) {
  return USERS.get(id);
}
```

```kotlin
fun find(id: @Nonnull String): @Nullable String? = users[id]
```

## @Secret

Marks a class or property that holds secret material. Readable by reflection; generated `toString` output is not
redacted. In Java it applies to classes only.

```java
@Secret
record Token(String value) {}
```

```kotlin
data class Credentials(val user: String, @Secret val token: String)
```

## Complete examples

The snippets above come from these two programs. The Java file runs on its own:

```java
// Annotations.java
import dev.elide.annotations.API;
import dev.elide.annotations.Delicate;
import dev.elide.annotations.Eager;
import dev.elide.annotations.Factory;
import dev.elide.annotations.Generated;
import dev.elide.annotations.Nonnull;
import dev.elide.annotations.Nullable;
import dev.elide.annotations.Secret;
import java.util.Arrays;
import java.util.Map;

public class Annotations {
  @API
  interface Greeter {
    String greet(String name);
  }

  @Factory
  static class GreeterFactory {
    static Greeter create() {
      return name -> "Hello, " + name;
    }
  }

  @Eager
  static class Startup {}

  @Secret
  record Token(String value) {}

  static final Map<String, String> USERS = Map.of("ada", "Ada Lovelace");

  static @Nullable String find(@Nonnull String id) {
    return USERS.get(id);
  }

  @Delicate
  static void reset() {}

  @Generated
  static String banner() {
    return "generated";
  }

  public static void main(String[] args) {
    System.out.println(GreeterFactory.create().greet("Ada"));
    System.out.println(find("ada") + ", " + find("bob"));
    reset();
    for (Class<?> type : new Class<?>[] {Greeter.class, GreeterFactory.class, Startup.class, Token.class}) {
      System.out.println(type.getSimpleName() + " " + Arrays.toString(type.getAnnotations()));
    }
    System.out.println(new Token("s3cr3t"));
  }
}
```

```bash
elide run Annotations.java
```

```text
Hello, Ada
Ada Lovelace, null
Greeter [@dev.elide.annotations.API()]
GreeterFactory [@dev.elide.annotations.Factory()]
Startup []
Token [@dev.elide.annotations.Secret()]
Token[value=s3cr3t]
```

The Kotlin program is a project, because `@Inject` and `@Singleton` need `jakarta.inject`, which a project adds:

```pkl
// elide.pkl
amends "elide:project.pkl"

name = "annotations"

jvm {
  main = "MainKt"
}

dependencies {
  maven {
    packages { "jakarta.inject:jakarta.inject-api:2.0.1" }
  }
}
```

```kotlin
// src/main/kotlin/Main.kt
import dev.elide.annotations.API
import dev.elide.annotations.Delicate
import dev.elide.annotations.Eager
import dev.elide.annotations.Factory
import dev.elide.annotations.Generated
import dev.elide.annotations.Inject
import dev.elide.annotations.Nonnull
import dev.elide.annotations.Nullable
import dev.elide.annotations.Secret
import dev.elide.annotations.Singleton

@API
interface Greeter {
  fun greet(name: String): String
}

@Singleton
class Polite @Inject constructor() : Greeter {
  override fun greet(name: String) = "Hello, $name"
}

@Factory
object GreeterFactory {
  fun create(): Greeter = Polite()
}

@Eager
class Startup

data class Credentials(val user: String, @Secret val token: String)

val users = mapOf("ada" to "Ada Lovelace")

fun find(id: @Nonnull String): @Nullable String? = users[id]

@Delicate
fun reset() {}

@Generated
fun banner() = "generated"

@OptIn(Delicate::class)
fun main() {
  println(GreeterFactory.create().greet("Ada"))
  println("${find("ada")}, ${find("bob")}")
  reset()
  for (type in listOf(Greeter::class, Polite::class, GreeterFactory::class, Startup::class)) {
    println("${type.simpleName} ${type.java.annotations.filterNot { it is Metadata }}")
  }
  println(Credentials("ada", "s3cr3t"))
}
```

```bash
elide run
```

```text
Hello, Ada
Ada Lovelace, null
Greeter [@dev.elide.annotations.API()]
Polite [@jakarta.inject.Singleton()]
GreeterFactory [@dev.elide.annotations.Factory()]
Startup []
Credentials(user=ada, token=s3cr3t)
```

## Samples

- [Elide annotations in Kotlin](https://elide.help/docs/code-samples#elide-annotations-in-kotlin)
