# Java SDK


Connect your Java app to customer licensing with `ActivationClient`. This guide takes you from SDK installation to customer approval and an actual permission check.

An activation request identifies the app installation to approve. The customer selects an owned license in the app-branded portal. The resulting activation response is signed and bound to that request and device. One license reserves one active registration.

## 1. Install the SDK

```xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>kr.signox.samples</groupId><artifactId>licensed-export</artifactId><version>0.3.1-SNAPSHOT</version>
  <properties><maven.compiler.release>8</maven.compiler.release><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties>
  <dependencies><dependency><groupId>kr.signox</groupId><artifactId>signox-sdk</artifactId><version>0.3.1-SNAPSHOT</version></dependency></dependencies>
  <build><plugins>
    <plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.13.0</version></plugin>
    <plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-dependency-plugin</artifactId><version>3.8.1</version></plugin>
    <plugin><groupId>org.codehaus.mojo</groupId><artifactId>exec-maven-plugin</artifactId><version>3.6.3</version></plugin>
  </plugins></build>
</project>
```

```bash
mvn -U compile
```

Use the package registry configured for your environment. Install the SDK as an application dependency; the sample ZIP contains source and is not the SDK installation method.

Runtime: Java 8+; JDK 17+ and Maven 3.6.3+ for the example build.

Check the SDK version Maven resolved for this project. The output should include kr.signox:signox-sdk:jar:0.3.1-SNAPSHOT. Installing a JDK does not install this project dependency.

```bash
mvn dependency:tree -Dincludes=kr.signox:signox-sdk
```

## 2. Prepare your product and test license

Open the vendor dashboard, choose a product, and open its settings. Use the UUID in that product detail URL and download its public key. Create a policy with the demo_export boolean feature enabled, issue a test license, and register the key to a test customer in the customer portal.

| Input | Where to get it and how it is used |
|---|---|
| `SIGNOX_API_URL` | API address of the environment you are testing |
| `SIGNOX_PRODUCT_ID` | UUID in the vendor product detail URL |
| `SIGNOX_PUBLIC_KEY_FILE` | Path to the downloaded product SPKI PEM file |
| `.signox-state` | The example creates this private directory; keep it across restarts |

Set the three environment variables in the shell that runs the example. Use the pinned product public key from the trusted application distribution. Customer passwords belong only in the customer portal, and license keys are selected there rather than hard-coded in the app.

## 3. Create the activation example

Save the complete code below in `src/main/java/App.java` and keep the same working directory for each command.

```java
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import kr.signox.sdk.*;
public class App {
  public static void main(String[] args) throws Exception {
    String api = System.getenv("SIGNOX_API_URL"), product = System.getenv("SIGNOX_PRODUCT_ID"), keyFile = System.getenv("SIGNOX_PUBLIC_KEY_FILE");
    if (api == null || product == null || keyFile == null)
      throw new IllegalArgumentException("Set SIGNOX_API_URL, SIGNOX_PRODUCT_ID and SIGNOX_PUBLIC_KEY_FILE");
    String pem = new String(Files.readAllBytes(Paths.get(keyFile)), StandardCharsets.UTF_8);
    ActivationClient client = new ActivationClient(product,
      SignoxConfig.builder(pem).baseUrl(api).build(), new File(".signox-state"));
    String command = args.length == 0 ? "start" : args[0];
    if (command.equals("start")) System.out.println(client.start().portalUrl);
    else if (command.equals("request")) System.out.println(client.createRequest());
    else if (command.equals("poll")) {
      ActivationClient.Progress progress = client.poll();
      System.out.println(progress.status);
      if (progress.result != null && !progress.result.isValid()) System.exit(2);
    }
    else {
      LicenseResult result = command.equals("apply")
        ? client.applyResponse(new String(Files.readAllBytes(Paths.get(args[1])), StandardCharsets.UTF_8)) : client.validate();
      boolean allowed = result.isValid() && result.getFeatureBool("demo_export", false);
      System.out.println("code=" + result.getCode() + ", exportAllowed=" + allowed);
      if (!result.isValid()) System.exit(2);
    }
  }
}
```

The example uses the same client for browser approval and file exchange. The SDK selects its device runtime; the app does not choose a device-provider type.

## 4. Approve this device in the customer portal

```bash
mvn compile exec:java -Dexec.mainClass=App -Dexec.args="start"
```

Open the returned portalUrl in the system browser. Check the app name and device name, sign in with the customer account, select an owned license, and choose the activation button. If another device is registered, the portal offers a reasoned transfer request for vendor review.

```bash
mvn compile exec:java -Dexec.mainClass=App -Dexec.args="poll"
```

Before approval, status is pending. Call again after approval; poll no faster than once every 3 seconds. A pending result means the request is still waiting, not that licensing succeeded.

## 5. Check the permission before running your feature

```bash
mvn compile exec:java -Dexec.mainClass=App -Dexec.args="status"
```

A successful check returns VALID or IN_GRACE_PERIOD. The example prints exportAllowed=true only when the license is valid and demo_export is true. A valid license without that feature must not write the CSV. Add this check inside your actual export operation, not only on the startup screen.

[Run the sample that writes a CSV file](/en/samples/java/)

## Activate from another computer

If the device cannot connect, export a request, import it at the customer portal /activate page on a connected computer, and bring activation.signox back to the original app. Pass the response text to applyResponse, not the path. The runnable example reads the file for you.

```bash
mvn compile exec:java -Dexec.mainClass=App -Dexec.args="request"
mvn compile exec:java -Dexec.mainClass=App -Dexec.args="apply activation.signox"
```

For a reliable request file, use the language sample activation-request request.signox command. Do not redirect Maven build logs into a request file.

Disconnected application requires a supported, trusted device key. A computer without it needs connectivity when applying the response. See the runtime and device requirements before distributing your app.

[Check offline runtime requirements](/en/guides/activation-offline/)

## Constructor and options

| Name | Type | Required / default | Description |
|---|---|---|---|
| `productId` | `String` | Required | Product UUID passed to ActivationClient |
| `config` | `SignoxConfig` | Required | Public key and server connection settings |
| `stateDir` | `File` | Required | Private persistent directory for this installation |
| `baseUrl` | `String` | https://api.signox.kr | SignoxConfig.Builder option |
| `timeoutMs` | `int` | 10000 ms | Connection/read timeout |
| `tsToleranceSeconds` | `long` | 300 seconds | Allowed clock difference from the server |
| `offlineGraceDays` | `long` | 365 days | Local cap on server network grace; zero disables cache grace |

The local grace cap cannot increase the signed server period. Expiry grace and network grace are different; neither allows an initial activation from an empty cache.

### Trusted runtime override

The default selects the OS module automatically. For a controlled device test or an application-owned runtime, use `new ActivationClient(productId, config, stateDir, executable, args...)`. Only a runtime shipped by the trusted application or explicitly supplied by the test environment may be used. Never execute a path from customer JSON or an activation response. The samples accept SIGNOX_OFFLINE_RUNTIME and SIGNOX_OFFLINE_EXECUTABLE for the supplied test wrapper. An override does not bypass device trust or signature verification.

## Methods and return values

| Signature | Return type | Behavior |
|---|---|---|
| `start()` | `ActivationClient.Session` | Register a request and return the browser URL and requestId. |
| `createRequest() / createRequest(name, renew)` | `String` | Return public request JSON. name labels the device; renew replaces the request while retaining the installation credential. |
| `poll()` | `ActivationClient.Progress` | Return pending before approval; after approval, apply the result and return its validation. |
| `applyResponse(content)` | `LicenseResult` | Read response TEXT, verify request/device binding, and apply it. |
| `validate()` | `LicenseResult` | Check license state and current feature permissions. |
| `deactivate()` | `LicenseResult` | Release a connected registration; protected grants require reviewed transfer. |

Node methods are asynchronous. Java, C# and Python methods are synchronous; run network and device work off the UI thread. A LicenseResult carries validity, code and features. Exceptions indicate request/state/import problems; do not turn them into successful permission checks.

## Handle failures

| Symptom / code | Cause | Action and expected result |
|---|---|---|
| `STATE_INVALID` | Saved installation state is missing/corrupt | Restore its private state, or create a request in a new installation and request transfer. |
| `DEVICE_TRUST_REQUIRED` | The portal cannot issue to an unverified device key | The operator must compare the key on the real device through a trusted channel, then enroll it. |
| `TRANSFER_REQUIRED` | A different device is registered or secure return is unavailable | Request transfer from the destination device; the vendor reviews that exact request. |
| `ACTIVATION_RESPONSE_INVALID` | Wrong file, signature, request or device | Use the response issued for this request on the original device. Never edit its contents. |
| `REQUEST_EXPIRED` | Seven-day request window ended | Create a renewed request and repeat customer approval. |
| `NETWORK_ERROR` | A temporary connection/service failure | Retry the connection; only a prior valid signed grant/cache can allow disconnected use. |
| `REQUEST_ERROR` | Invalid URL, input or access | Fix the request; do not retry a permanent HTTP error forever. |

A signed suspension/revocation/expiry denial must stay denied after a later network failure. The old offline grant does not override it. Permanently disconnected grants cannot receive an immediate remote revocation. A format or file deletion is not proof that old backups stopped working.

[Integration verification](/en/guides/integration-checks/) · [Device recovery and transfer](/en/guides/hwid/)

If response delivery succeeds but local application validation fails, polling returns application_failed with its invalid result. Only applied plus a valid result confirms successful application.

Keep the runtime override identical between request creation and application. Browser/file transport does not select device protection; changing a request name also does not change its provider. OFFLINE_NOT_SUPPORTED after a verified response means the required local runtime cannot be used; restore that environment rather than request unrelated trust enrollment.
