Browse the library
Documentation libraryGet started by language
Get started with Java and Kotlin
This guide takes a Java program from nothing to its first activated license, and on to the build you hand to customers: install the SDK, a few lines of code, a test run, the result on the dashboard, errors, and release. Kotlin and other JVM languages call the same functions; there is a note at the end.
5 min read
What you need
- JDK 17 or later. At run time the SDK needs nothing beyond the JDK: Ed25519 and HTTP come from the JDK, and the SDK reads JSON itself.
- An app on the dashboard. Its Integration › Connect software page has the API address, the App ID, the public key and a Make a test key button. The API address ends in
/api, with no/v1:https://<your ClavisAUH site>/api.
1. Install the SDK
Download clavisauh-1.0.0.jar from Downloads and check its SHA-256 as that page shows. Then pick one of three ways: Maven, Gradle, or no build tool at all (step 3 uses this last one).
# Maven: install the jar into the local repository once
mvn install:install-file -Dfile=clavisauh-1.0.0.jar -DgroupId=clavisauh -DartifactId=clavisauh -Dversion=1.0.0 -Dpackaging=jar
then add the dependency to your pom.xml:
<dependency>
<groupId>clavisauh</groupId>
<artifactId>clavisauh</artifactId>
<version>1.0.0</version>
</dependency>
With Gradle, put the jar in a libs/ folder and add implementation(files("libs/clavisauh-1.0.0.jar")) to dependencies. Without a build tool, put the jar on the class path: java -cp clavisauh-1.0.0.jar ….
The SDK is not on Maven Central yet; once it is, this guide will show that way too.
2. The minimal code
The blocks below are parts of one file; the whole file, with its imports and helpers, is in step 3.
Create the client once, when your software starts. The API address, App ID and public key are the three values on the Connect software page: the sample reads them from environment variables, your software writes them in its code. The device key lives in a file: sessions and offline tokens are bound to it, while the seat on the license follows the machine's hashed identifiers:
ClavisClient client = ClavisClient
.builder(env("CLAVIS_BASE_URL", ""), env("CLAVIS_APP_ID", ""), List.of(env("CLAVIS_APP_KEY", "")))
.device(Device.defaultDevice(null))
// The same key every run: sessions and offline tokens stay with this machine.
.deviceKey(DeviceKey.loadOrCreate(state.resolve("device.key")))
.appVersion("1.0.0")
.build();
Activate the customer's key. Without a network, the offline token saved last time keeps the software running:
Grant grant = null;
try {
client.init();
grant = client.activate(env("CLAVIS_LICENSE", ""));
} catch (ClavisException e) {
// No answer (network_error, timeout...): a token saved last time keeps the software working.
String token = Files.exists(saved) ? Files.readString(saved) : null;
if (!e.retryable() || token == null || client.verifyOffline(token).isEmpty()) {
System.out.println(e.userMessage(lang));
System.out.println("FAIL " + e.code());
System.exit(1);
}
System.out.println("Offline: using the license saved on this machine");
}
The public key only checks signatures, so embedding it in your software is safe. Never embed any secret key.
3. Run it
- On the Connect software page, select Make a test key. A TEST key expires after 24 hours and does not count against your limits.
- Save the whole file below as
K1Key.javanext to the jar, set the four settings and run the source file directly, with no compile step:
// K1 · License key only: activate on this machine, keep the session alive, work offline for a while.
//
// Run: CLAVIS_BASE_URL=... CLAVIS_APP_ID=... CLAVIS_APP_KEY=... CLAVIS_LICENSE=...
// java -cp clavisauh-1.0.0.jar K1Key.java
import clavisauh.ClavisClient;
import clavisauh.ClavisException;
import clavisauh.Device;
import clavisauh.DeviceKey;
import clavisauh.Grant;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class K1Key {
static String env(String name, String fallback) {
String value = System.getenv(name);
return value == null || value.isEmpty() ? fallback : value;
}
public static void main(String[] args) throws Exception {
// Messages in Vietnamese need a UTF-8 console (the default on Linux and in Windows Terminal).
System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out), true, StandardCharsets.UTF_8));
String lang = env("CLAVIS_LANG", "en");
Path state = Path.of(env("CLAVIS_STATE_DIR", ".clavis"));
Path saved = state.resolve("offline.token");
ClavisClient client = ClavisClient
.builder(env("CLAVIS_BASE_URL", ""), env("CLAVIS_APP_ID", ""), List.of(env("CLAVIS_APP_KEY", "")))
.device(Device.defaultDevice(null))
// The same key every run: sessions and offline tokens stay with this machine.
.deviceKey(DeviceKey.loadOrCreate(state.resolve("device.key")))
.appVersion("1.0.0")
.build();
Grant grant = null;
try {
client.init();
grant = client.activate(env("CLAVIS_LICENSE", ""));
} catch (ClavisException e) {
// No answer (network_error, timeout...): a token saved last time keeps the software working.
String token = Files.exists(saved) ? Files.readString(saved) : null;
if (!e.retryable() || token == null || client.verifyOffline(token).isEmpty()) {
System.out.println(e.userMessage(lang));
System.out.println("FAIL " + e.code());
System.exit(1);
}
System.out.println("Offline: using the license saved on this machine");
}
if (grant == null) {
System.out.println("OK k1 offline");
return;
}
Object until = grant.license().expiresAt() != null ? grant.license().expiresAt() : "forever";
System.out.println("License " + grant.license().status() + " until " + until);
// Every few minutes while the software runs; sessions end after an hour without one.
client.heartbeat();
if (client.offlineToken() != null) Files.writeString(saved, client.offlineToken());
client.logout();
System.out.println("OK k1 " + grant.license().status());
}
}
export CLAVIS_BASE_URL="<API address>"
export CLAVIS_APP_ID="<App ID>"
export CLAVIS_APP_KEY="<public key>"
export CLAVIS_LICENSE="<test key>"
java -cp clavisauh-1.0.0.jar K1Key.java
On Windows, in PowerShell:
$env:CLAVIS_BASE_URL = "<API address>"
$env:CLAVIS_APP_ID = "<App ID>"
$env:CLAVIS_APP_KEY = "<public key>"
$env:CLAVIS_LICENSE = "<test key>"
java -cp clavisauh-1.0.0.jar K1Key.java
The starter project (Download the starter (.zip) on the dashboard's Connect software page, once Java is picked; it is not on the Downloads page) runs this scenario with ./run.sh <key> or .\run.cmd <key>.
It prints License active until <date>, then OK k1 active on the last line. Run it again on the same machine and it still succeeds without using another seat: the server knows the machine by its hashed identifiers (Devices, HWID and transfers). The device key, which signs the requests, stays in the .clavis folder under the folder you run it from (or in CLAVIS_STATE_DIR). A container started afresh for each run looks like a new machine every time, see Questions.
4. See it on the dashboard
- Integration › Connect software: step 4 turns to Connected, with
java/1.0.0and the machine's operating system. - Licensing & releases › Licenses: the key goes from Unused to Active.
- End users › Devices & sessions: the machine that ran it, and the session it opened.
5. Handle errors
Every refusal is a ClavisException with three things you need:
e.code(): a code from the error codes table, such asinvalid_credentialsordevice_limit.e.userMessage("en"): a sentence for the person using your software, in English or"vi", ready to show.e.retryable():truewhen no answer arrived (no network, too slow, a busy server); trying again later may work.
When e.verification() is true, the answer did not carry your app's valid signature (code unsigned_response, invalid_response_signature, unknown_key or stale_response). Treat it as no answer at all; usually the API address or the public key in your software is wrong.
While your software runs, send a heartbeat regularly to keep the session, every 5 to 30 minutes or so: a session ends after 60 minutes without one.
// Every few minutes while the software runs; sessions end after an hour without one.
client.heartbeat();
Save the latest offline token, so the software still opens next time without a network:
if (client.offlineToken() != null) Files.writeString(saved, client.offlineToken());
Offline tokens come on every plan and last 7 days by default; Premium chooses the length, and Online-Only Mode turns them off. See Offline licenses. To see every call while tracking a problem down, add .debug(ClavisClient.DEBUG_STDERR) to the builder: each call and its outcome go to stderr, keys show only their first and last four characters, and passwords never appear.
6. Package and release
- Write the API address, App ID and public key in your code or in a config file shipped with the build. Customers' machines need no environment variables.
- Put the state folder (device key, offline token) where the user can write, one per user:
%APPDATA%\YourAppon Windows,~/Library/Application Support/YourAppon macOS,~/.config/your-appon Linux. Never in the install folder. - Pass
.appVersion(...)as the version you release: the server uses it to block builds that are too old and to announce new ones, see Signed releases and updates. - Ship the SDK jar with your software (one merged jar, or the SDK jar beside yours on the class path). The JDK's
jpackagemakes an installer with Java included, for customers who have none. - Run the release build to
OK k1 activeon a clean machine before you release it. - Before you sell, turn on Publicly released in Integration › App settings. Every TEST key stops working, so a test key that got out cannot be used.
With Kotlin
Kotlin uses the same jar and the same functions. The Gradle Kotlin DSL adds implementation(files("libs/clavisauh-1.0.0.jar")); the calls read like the Java ones in Kotlin's syntax, for example ClavisClient.builder(baseUrl, appId, listOf(publicKey)) then .device(Device.defaultDevice(null)), .deviceKey(DeviceKey.loadOrCreate(path)), .build(). The SDK has not been tried on Android.
Questions
Does the software need a network every time it opens? Activation and heartbeats do. Without a network, the saved token keeps the software running for 7 days by default, or the length chosen on Premium, checked on the machine without the server.
Can several threads share one client? Better not: the client is not safe for concurrent use. Keep one client per thread, or guard it when several places call it. In Swing or JavaFX, keep calls off the interface thread, since each one waits for the network.
How do I limit features by license plan? client.has("feature_name") tells whether the license grants that feature; create features in License plans and entitlements.
What if a customer reinstalls the system or changes machines? After a reinstall on the same machine, the server still recognizes it from its hashed hardware identifiers. A new machine takes a new seat; when none is left the customer gets device_limit, and you Reset devices on the Licenses page. See Devices, HWID and transfers.