# Java HTTP-native compatibility client

Use `LockwellNativeClient` when a Java or Spring service needs the versioned HTTP/JSON compatibility data plane at
`/api/v1/`. It is intentionally distinct from the primary binary LNW/1 client in
`com.lockwell.sdk.springwire.LockwellNativeWireClient`; it does not silently switch transports. Both surfaces use 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](/sdks/java).

## Install

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

The package targets JDK 25. For the binary transport and Spring Boot 4.1 autoconfiguration, use the
`com.lockwell:lockwell-spring-boot-starter` dependency and see the [Java SDK wire section](/sdks/java#lnw1-native-wire).

## 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 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:

```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();
```

Streaming native PUT, GET, and multipart part overloads accept a
`ProgressListener`. `TransferProgress` reports cumulative bytes, known or
unknown totals, direction, and part number; listener execution applies
backpressure and listener failure cancels the source/response. The native
builder also accepts `responseMetadataListener` for successful request-id and
trace headers. One-shot streams are not transparently resumed; use range/ETag
or multipart list/abort plus application-owned atomic files when recovery is
needed.

## 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](/guide/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](/sdks/java) for the complete package reference.
- [Errors and retries](/guide/errors-and-retries) for cross-language retry behavior.
- [Conditional writes and idempotency](/guide/conditional-writes) for write safety patterns.
- [Upload and download](/guide/data-operations) for object I/O examples.
