Skip to content

Java native client

Use LockwellNativeClient when a Java or Spring service is built directly for Lockwell instead of porting an existing AWS S3 integration. It talks to the JSON data plane at /api/v1/ on the public listener, keeps SigV4 and XML out of the app path, and still uses the same tenant isolation, encryption, quota, retention, audit, and scope checks as the S3 surface.

This page is the production checklist for JVM services such as TangibleShift ERP. The full method reference remains on the Java SDK page.

Install

xml
<dependency>
  <groupId>com.lockwell</groupId>
  <artifactId>lockwell-sdk</artifactId>
  <version>0.2.2</version>
</dependency>

The package requires Java 21 or newer and has no third-party runtime dependencies.

Configure the client

java
import com.lockwell.sdk.RetryPolicy;
import com.lockwell.sdk.nativeapi.LockwellNativeClient;
import java.net.http.HttpClient;
import java.time.Duration;

LockwellNativeClient nativeClient = LockwellNativeClient.builder()
    .endpoint("https://objects.example.com") // public listener; /api/v1 is added by the SDK
    .accessKeyId(System.getenv("LOCKWELL_ACCESS_KEY_ID"))
    .secretKey(System.getenv("LOCKWELL_SECRET_KEY"))
    .httpClient(HttpClient.newHttpClient())
    .requestTimeout(Duration.ofSeconds(30))
    .retryPolicy(RetryPolicy.defaults())
    .build();

endpoint is the public object listener, not the admin listener. The client appends /api/v1 itself, so pass the base origin such as https://objects.example.com.

Token lifecycle

The native client accepts the same access-key id and secret used by the S3 clients. It mints a short-lived bearer token with POST /api/v1/auth/token, caches it until shortly before expiry, and refreshes it under a single-flight lock so concurrent callers share one in-flight mint. If a request receives 401, the client forces one token re-mint and replays that request once inside the current attempt.

This means application code should not cache bearer tokens separately. Store the access-key id and secret in the service secret store, build one shared client per tenant credential set, and let the SDK handle token refresh.

Native retries

The Java native client uses RetryPolicy.defaults() by default. It retries transient failures only when the request can be replayed safely:

Request shapeRetried by default?Why
GET, HEAD, DELETEYesIdempotent HTTP methods.
PUT and POST writesOnly with a keyRequires an Idempotency-Key header.
Streaming upload supplierOnly when keyedThe supplier must be able to open a fresh stream.
Other methodsNoNot known to be replay-safe.

Transient failures are transport errors, 429, and 5xx responses. A server Retry-After header is honored when it is present as delta-seconds or an HTTP date. The SDK never lets Retry-After shorten the local backoff, and it caps the server-requested wait so one bad peer cannot hold the caller forever.

Turn retries off when a caller owns the retry loop:

java
LockwellNativeClient oneAttempt = LockwellNativeClient.builder()
    .endpoint(endpoint)
    .accessKeyId(accessKeyId)
    .secretKey(secretKey)
    .retryPolicy(RetryPolicy.disabled())
    .build();

Write idempotency

For putObject, pair the idempotency key with a checksum. The key makes a retry replay-safe, and the checksum lets the server prove the replayed body is the same payload before it collapses the duplicate.

java
import com.lockwell.sdk.nativeapi.NativeTypes.PutOptions;
import java.security.MessageDigest;
import java.util.Base64;

byte[] body = invoiceJson.getBytes(java.nio.charset.StandardCharsets.UTF_8);
String sha256 = Base64.getEncoder().encodeToString(
    MessageDigest.getInstance("SHA-256").digest(body));

nativeClient.putObject("erp-documents", "invoices/2026-0001.json", body,
    new PutOptions()
        .contentType("application/json")
        .idempotencyKey("invoice-2026-0001")
        .checksum("sha256", sha256));

For Supplier<InputStream> uploads, the supplier must be repeatable. A supplier that opens a file path is replayable; a supplier that returns an already-consumed stream is not.

Request timeouts

requestTimeout(Duration) sets the JDK HttpRequest.timeout for each HTTP attempt. If retries are enabled, total wall time can include more than one request timeout plus retry backoff. For large uploads, size the timeout for the full upload attempt. For streaming downloads, the timeout covers the request until the response arrives; the caller owns the pace of reading and closing the returned stream.

Use a shared HttpClient if your service has strict TLS, proxy, or pooling requirements:

java
HttpClient http = HttpClient.newBuilder()
    .connectTimeout(Duration.ofSeconds(5))
    .build();

LockwellNativeClient nativeClient = LockwellNativeClient.builder()
    .endpoint(endpoint)
    .accessKeyId(accessKeyId)
    .secretKey(secretKey)
    .httpClient(http)
    .requestTimeout(Duration.ofSeconds(30))
    .build();

ERP defaults

For systems that write business documents, exports, invoices, attachments, or restore artifacts:

  • Use the native client for new Lockwell integrations and the S3 client only for S3-porting work.
  • Keep one shared client per tenant credential set instead of rebuilding a client per request.
  • Set requestTimeout explicitly.
  • Keep the native retry default unless the service has its own bounded retry framework.
  • Add an idempotency key and checksum to every write that might be retried.
  • Use ifAbsent() for create-only writes and ifMatch(etag) for optimistic updates.
  • Close every GetResult in a try-with-resources block.

Signed-URL constraints for browser-direct flows

When the ERP mints a signed URL for a browser upload or download, pin the properties the browser cannot be trusted to send correctly. Pass SignedUrlOptions to signUrl / signUrlResult (or to the kit's signedUploadUrl / signedDownloadUrl): contentType (pins the stored type), contentLengthMax (rejects oversize), checksum (verifies the body), idempotencyKey (collapses retries), keyPrefix (PUT-only prefix scope), and the GET responseContentType / responseContentDisposition overrides. These are HMAC-covered and enforced by the server at dispatch time. See signed URLs for the full set.

For ERP deployments with separate internal and browser-reachable origins, set signedUrlPublicOrigin on the app kit builder; set signedUrlMaxTtl for a client-side TTL cap.

ERP live conformance

The production test harness now includes a Java ERP live conformance runner. It composes the Java app kit, admin client, and native client against the packaged Lockwell image to prove the minimum cutover flow: idempotent tenant provisioning, object-lock fiscal writes, signed browser GET/PUT URLs, large streaming artifacts, typed denial/error mapping, rotate/revoke and tenant-disable denial, and audit request-id correlation.

Backup and restore remain operational CLI drills (backup-plan, backup-verify, restore dry-run/verify) documented in docs/backup-restore.md, not Java native SDK helpers.

Released under the Apache-2.0 License. License