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
<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
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 shape | Retried by default? | Why |
|---|---|---|
GET, HEAD, DELETE | Yes | Idempotent HTTP methods. |
PUT and POST writes | Only with a key | Requires an Idempotency-Key header. |
| Streaming upload supplier | Only when keyed | The supplier must be able to open a fresh stream. |
| Other methods | No | Not 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:
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.
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:
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
requestTimeoutexplicitly. - 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 andifMatch(etag)for optimistic updates. - Close every
GetResultin 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.
Related pages
- Java SDK for the complete package reference.
- Errors and retries for cross-language retry behavior.
- Conditional writes and idempotency for write safety patterns.
- Upload and download for object I/O examples.