> ## Documentation Index
> Fetch the complete documentation index at: https://docs.varianz.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Java SDK

> Instrument Java services with @VPoint annotations: the annotation processor, the varianz-agent, registry binding, Maven setup, and troubleshooting.

The Java SDK instruments methods with `@VPoint`. Three pieces cooperate: the **annotation processor** generates schema-bearing aggregate classes at compile time, the **`varianz-agent`** intercepts annotated methods at class-load time, and **`RegistryBinding`** connects the process to the registry. The [Gradle plugin](/installation#java) wires all three.

## Canonical imports

```java theme={null}
import io.varianz.annotations.vpoint.VPoint;          // the annotation
import io.varianz.annotations.vpoint.VPointControl;   // per-VPoint handle
import io.varianz.annotations.vpoint.VPoints;         // auto-wired aggregate
import io.varianz.annotations.vpoint.param.VarianzId; // session-id marker
import io.varianz.annotations.struct.Struct;          // DTO struct marker
import io.varianz.annotations.struct.Function;        // context-function marker
import io.varianz.binding.RegistryBinding;            // registry connection
```

These are the only correct paths — `io.varianz.annotations.VPoint` (missing the `vpoint` package), `io.varianz.sdk.VPoint`, and similar guesses do not exist.

## Defining VPoints

```java theme={null}
public class PricingService {
    @VPoint(name = "pricing/calculate")
    public double calculatePrice(double amount, double discount) {
        return amount * (1 - discount);
    }
}
```

* **Always set an explicit `name`.** Omitted names derive from package/class/method names and break under refactoring.
* Any class and method visibility works — the agent rewrites bytecode, so `private`/`final`/`synchronized` methods are fine. (One limit: for private *inner* classes, the generated aggregate can't expose direct typed invocation; stages by name still work.)
* Plain beans and public-field DTOs are introspected automatically — fields from getters or public fields, constructors matched by type compatibility (static factories like `Quote.of(...)` included). No `@Struct` needed for typical types. Field names convert camelCase → snake\_case in CEL (`basePrice` → `base_price`).
* For gRPC handlers, extract business logic into a plain-typed `@VPoint` method — keep `StreamObserver` out of schemas. See [the pattern](/guides/instrument-a-service#grpc-handlers).

The processor generates a `PricingServiceVPoints` aggregate in the same package, wrapping the method with stage execution and embedding the VPD schema.

## How interception works

The agent transforms each `@VPoint` method at class load: the original body is cloned to `<method>$varianzDirect` (keeping line tables, so breakpoints still land in your source), and the method itself becomes a dispatch stub into the stage chain. Calling the method normally routes through the pipeline — no `bind()`-proxy calls needed:

```java theme={null}
PricingService service = new PricingService();
double price = service.calculatePrice(100.0, 0.1);  // stages apply automatically
```

With no stages registered, dispatch reads one volatile and calls the clone directly — the no-stage path is essentially free after JIT warmup.

If the agent is *not* attached, the SDK prints one loud warning at first class load and `@VPoint` methods run their original bodies. The Gradle/Maven plugins attach the agent for test and run tasks; **production launchers must attach it explicitly**:

```kotlin theme={null}
application {
    applicationDefaultJvmArgs = listOf(
        "-javaagent:\$APP_HOME/lib/varianz-agent-0.2.1-agent.jar",
        "--enable-native-access=io.varianz.native_loader",  // Java 22+
    )
}
```

## Connecting to the registry

Call `RegistryBinding.init()` early — before your server starts serving — then `bind()` each service instance to register its VPDs:

```java theme={null}
public static void main(String[] args) {
    String addr = System.getenv().getOrDefault("VARIANZ_REGISTRY_ADDR", "http://localhost:50051");
    RegistryBinding.init(addr);          // idempotent

    AdServiceImpl adService = new AdServiceImpl();
    AdServiceImplVPoints.bind(adService);

    Server server = ServerBuilder.forPort(9555)
        .intercept(new VarianzServerInterceptor())   // session propagation
        .addService(adService)
        .build()
        .start();
}
```

In Spring Boot, do both in a `@PostConstruct`. Session propagation uses the pull-based `@VarianzId @Function(global = true)` pattern — the full setup is in [Propagate sessions](/guides/propagate-sessions#java-and-kotlin).

### Control annotations

```java theme={null}
@VPointControl(name = "pricing/calculate")
private VPointHandle calculateControl;   // enable/disable stages, query state

@VPoints
private PricingServiceVPoints vpoints;   // auto-wired aggregate
```

`Aggregate.bind(service)` returning a typed proxy exists for advanced cases (test harnesses, programmatic stage composition) — normal code doesn't need it.

### Disabling interception

In order of preference: don't attach the agent (Gradle `weave.set(false)`, Maven `-Dvarianz.skipAgent=true`); disable at runtime with `-Dvarianz.proc.weave=false`; or opt out one method with `@VPoint(wrapped = false)`.

## Maven

```xml theme={null}
<repositories>
  <repository><id>varianz</id><url>https://pkgs.varianz.io/maven</url></repository>
</repositories>
<pluginRepositories>
  <pluginRepository><id>varianz</id><url>https://pkgs.varianz.io/maven</url></pluginRepository>
</pluginRepositories>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.varianz</groupId>
      <artifactId>varianz-bom</artifactId>
      <version>0.2.1</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>
    <groupId>io.varianz</groupId>
    <artifactId>varianz-starter</artifactId>
    <type>pom</type>
  </dependency>
  <dependency>
    <groupId>io.varianz</groupId>
    <artifactId>varianz-junit</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <annotationProcessorPaths>
          <!-- The `all` classifier bundles JavaParser and other processor deps -->
          <path>
            <groupId>io.varianz</groupId>
            <artifactId>java-processor</artifactId>
            <version>0.2.1</version>
            <classifier>all</classifier>
          </path>
        </annotationProcessorPaths>
      </configuration>
    </plugin>

    <!-- Resolves the agent JAR and exposes -javaagent:… as ${varianz.agentArgs},
         same idiom as jacoco's prepare-agent. -->
    <plugin>
      <groupId>io.varianz</groupId>
      <artifactId>varianz-maven-plugin</artifactId>
      <version>0.2.1</version>
      <dependencies>
        <dependency>
          <groupId>io.varianz</groupId>
          <artifactId>varianz-agent</artifactId>
          <classifier>agent</classifier>
          <version>0.2.1</version>
        </dependency>
      </dependencies>
      <executions>
        <execution>
          <id>prepare-varianz-agent</id>
          <goals><goal>prepare-agent</goal></goals>
        </execution>
      </executions>
    </plugin>

    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <configuration>
        <argLine>@{varianz.agentArgs}</argLine>
      </configuration>
    </plugin>
  </plugins>
</build>
```

On Java 22+, append `--enable-native-access=io.varianz.native_loader` to the `<argLine>`. To disable interception for a build, pass `-Dvarianz.skipAgent=true`.

## Native access (Java 22+)

The SDK loads its Rust runtime via `System.load`, a restricted method under JEP 472. All Varianz JNI loading funnels through the `io.varianz.native_loader` module, so one flag covers the SDK:

```
--enable-native-access=io.varianz.native_loader
```

The Gradle plugin injects it automatically for `Test`/`JavaExec` on Java 22+; Maven and hand-rolled launchers add it to their JVM args. Prefer this targeted grant over `ALL-UNNAMED`.

## Troubleshooting

* **`@VPoint` methods not intercepted** — almost always the agent isn't attached; look for the `[varianz] Varianz agent not attached` warning. Check `weave` (Gradle) or the `prepare-agent` execution + `@{varianz.agentArgs}` (Maven). For custom launchers, verify with `-Dvarianz.proc.debug=true` (the agent logs its transformer registration).
* **`NoClassDefFoundError` for a `...VPoints` class at first call** — the annotation processor didn't run at compile time, so the aggregate doesn't exist. With Gradle, `compileJava` should log `Note: [Varianz] Generated adapters...`.
* **`System.load` warnings on Java 24+** — the JVM fork is missing the native-access flag; see above.
