Skip to content

Java SDK

The Java 17+ SDK is com.dycrypt:dycrypt-license-sdk. It has no runtime dependencies and uses Java’s standard cryptography and HTTP APIs.

Until the package is published to a public registry, build it from the DYCRYPT SDK checkout:

Terminal window
cd sdk/java
mvn install
<dependency>
<groupId>com.dycrypt</groupId>
<artifactId>dycrypt-license-sdk</artifactId>
<version>0.1.0</version>
</dependency>
LicenseVerifier verifier = LicenseVerifier.builder()
.publicKeyPem(Files.readString(Path.of("product-public-key.pem")))
.productKey("your-product-key")
.build();
LicenseResult result = verifier.verify(
Files.readString(Path.of("customer.lic"))
);
if (!result.valid()) {
throw new IllegalStateException("Unlicensed: " + result.reason());
}

Build the verifier once and reuse it. It is immutable and holds only public verification material.

Offline and offline-activated Hybrid licences need the customer’s fingerprint before issue:

import com.dycrypt.license.Fingerprint;
if (args.length > 0 && "--fingerprint".equals(args[0])) {
System.out.println(Fingerprint.of());
return;
}

Run this on the machine that will use the licence and copy the complete DYC1.… output into the DYCRYPT issue form.

Copy the public key, product key, issuer URL, organisation slug, and check credentials from the product’s Quick Start Guide.

import com.dycrypt.license.*;
import java.nio.file.*;
import java.time.Duration;
LicenseVerifier verifier = LicenseVerifier.builder()
.publicKeyPem(Files.readString(Path.of("product-public-key.pem")))
.productKey("your-product-key")
.issuer("https://app.dycrypt.com", "your-org-slug")
.checkCredentials("<the product’s check id>", "dycc_...")
.policy(OnlinePolicy.ADVISORY)
.timeout(Duration.ofSeconds(5))
.build();

Omit issuer(...) and checkCredentials(...) for an Offline product. A malformed public key, a missing product key, or an issuer without credentials fails during construction instead of turning every licence into an unexplained invalid result.

The check token ships inside your application, so treat it as identifying rather than secret. What makes an answer trustworthy is the signature DYCRYPT returns with it: the verifier checks that receipt against the product’s public key before recording a successful check. requireReceipt(false) exists only for deployments predating signed answers, and turns the online check back into something a stub server can satisfy.

LicenseResult result = verifier.verify(
Files.readString(Path.of("customer.lic"))
);
enforce(result);

verify() never performs a network request and never throws for hostile licence input. A wrong machine, signature, product, or schema returns INVALID.

LicenseResult result = verifier.check(
Files.readString(Path.of("customer.lic"))
);
enforce(result);

check() verifies locally first and sends only a locally verified licence to DYCRYPT. Call it at startup and on the check interval configured for your product. The SDK does not start a background scheduler.

Keep the verifier for the lifetime of the process. If your application persists the time of a successful check across restarts, restore it with recordGoodCheck(licenceId, instant) before relying on the remaining offline window.

A renewed licence answers check() with valid() == false and online().status() == "ACTIVE" — the old string no longer verifies, but the issuer confirms a current one exists. If the product has turned on self-service fetch, ask for it directly instead of prompting the customer to install a new file by hand:

String current = verifier.fetchLatest(licenceText);
if (current != null) {
Files.writeString(Path.of("customer.lic"), current);
}

fetchLatest() presents the licence you already hold as the credential — the current one, or an earlier, superseded term for the same licence id — and returns null if the issuer refuses, is unreachable, or answers with something that does not itself verify. It runs the result back through verify() before returning it, so a caller never has to trust the transport. Call it only when you decide to, never automatically from inside check(). It is opt-in per product and refused once the licence you present is past its own expiry and grace; see the licence check API for the full protocol.

static void enforce(LicenseResult result) {
switch (result.status()) {
case ACTIVE -> { /* enable licensed functionality */ }
case GRACE -> warn("Licence expired; grace period is active");
case PENDING -> stop("Licence starts at " + result.startsAt());
case STALE -> stop("Reconnect to refresh the licence status");
case REVOKED -> stop("This licence was revoked");
case EXPIRED -> stop("This licence expired");
case INVALID, UNKNOWN -> stop("Licence rejected: " + result.reason());
}
}

ACTIVE and GRACE are the only valid statuses.

int maxUsers = result.getInt("maxUsers", 5);
String edition = result.getString("edition", "standard");
boolean exportEnabled = result.getBoolean("exportEnabled", false);
List<String> features = result.getStringList("features", List.of());

Typed accessors return your fallback for an absent or wrong-type claim and never coerce values silently.