Skip to main content
Current documentVerified

First unit — Java

Guide for SDK 0.9.0 and Java ≥25. The project uses Gradle. An exposition declaration, handler and configuration callback define the unit; it reaches the platform through Dapr.

Toolchain and dependencies​

Save build.gradle.kts. The toolchain setting prevents accidentally using an older JDK from the environment.

plugins {
java
application
}
java {
toolchain { languageVersion = JavaLanguageVersion.of(25) }
}
dependencies { implementation("dev.seeraos:unit-sdk:0.9.0") }
application { mainClass = "CatsUnit" }

In settings.gradle.kts, configure the administrator-provided private feed as exclusive for the group. Keep credentials in user file ~/.gradle/gradle.properties, under seeraosGiteaUsername and seeraosGiteaPassword. Do not place them in the project repository.

// settings.gradle.kts
dependencyResolutionManagement {
repositories {
exclusiveContent {
forRepository {
maven {
name = "seeraosGitea"
url = uri("https://registry.example.com/maven")
credentials(PasswordCredentials::class)
}
}
filter {
includeGroup("dev.seeraos")
}
}
mavenCentral()
}
}

Program​

Save src/main/java/CatsUnit.java. Imports complete the source guide fragment. Environment configuration supplies the Control Plane address, issuer and link key. The default identity store is in the working directory; ensure persistence.

import dev.seeraos.unit.sdk.error.UnitError;
import dev.seeraos.unit.sdk.error.UnitResult;
import dev.seeraos.unit.sdk.exposition.DeclaredObject;
import dev.seeraos.unit.sdk.exposition.DeclaredType;
import dev.seeraos.unit.sdk.exposition.ExpositionDeclaration;
import dev.seeraos.unit.sdk.exposition.Field;
import dev.seeraos.unit.sdk.hosting.RunningUnit;
import dev.seeraos.unit.sdk.hosting.UnitHost;
import dev.seeraos.unit.sdk.hosting.UnitManifest;
import java.net.URI;
import java.util.Map;
import java.util.Optional;

public final class CatsUnit {

private static final System.Logger LOG = System.getLogger(CatsUnit.class.getName());

public record Cat(String id, String name) {
}

public record CatsConfig(String greeting, String apiKey) {

@Override
public String toString() {
return "CatsConfig[greeting=" + greeting + ", apiKey=(redacted)]";
}
}

public static void main(String[] arguments) throws Exception {
DeclaredObject cat = DeclaredType.object("Cat")
.identity("id", Field.string().required())
.field("name", Field.string().maxLength(60).required())
.build();
ExpositionDeclaration exposition = ExpositionDeclaration.builder("cat", 1, "cats")
.get(cat, get -> get.response(cat))
.build();
Map<String, Cat> cats = Map.of("01J9ZC0000000000000000TOM0", new Cat("01J9ZC0000000000000000TOM0", "Tom"));

RunningUnit unit = UnitHost.builder()
.controlPlane(URI.create(System.getenv("SEERAOS_CONTROL_PLANE_URL")))
.instance("default", System.getenv("SEERAOS_LINK_KEY"))
.appId("cats")
.manifest(UnitManifest.of(UnitManifest.TenantAffinity.MULTI_TENANT, 1))
.exposition(exposition)
.issuer(URI.create(System.getenv("SEERAOS_ISSUER")))
.onCall("cat.get", call -> Optional.ofNullable(cats.get(call.entityId()))
.map(UnitResult::answering)
.orElseGet(() -> UnitResult.refusing(UnitError.notFound("Cat.NotFound", "No such cat."))))
.onConfigure(CatsConfig.class, (config, instance) -> LOG.log(System.Logger.Level.INFO,
"Instance " + instance.unitInstanceAssetId() + " is configured: " + config))
.build()
.start();
Runtime.getRuntime().addShutdownHook(new Thread(unit::close));
unit.awaitStopped();
}
}
gradle run

Lifecycle and modules​

start() returns after the listener starts; instances continue starting in the background. Port readiness does not imply active configuration. The shutdown hook closes the unit. CatsConfig.toString() hides its secret field; the default record representation would print its value.

With JPMS, the module requires dev.seeraos.unit.sdk. Export or open your record packages to the SDK module so reflection can use public accessors and constructors. These directives are unnecessary on the classpath.

A release publishes the exposition from its declaration. Maintain its artifact with the release process rather than writing another contract description manually. The example implements no event publication; adding it requires declarations and messaging APIs from the selected version. Troubleshooting covers refusals, identity and configuration.