Install and use the Qoder Cloud Agents Java SDK, including builders, synchronous and asynchronous calls, streaming responses, resource management, retries, and timeouts.
The Qoder Cloud Agents Java SDK provides typed synchronous and asynchronous clients, uses builders for configuration and parameters, and consistently handles authentication, pagination, retries, timeouts, and SSE streaming responses.
Requires Java 8 or later.
Add the following to
Use Maven Central and add a dependency with a pinned version:
Save your personal access token (PAT) in an environment variable:
Use the Forward client to create a template and identity, start a session, and send a message:
Use the Managed client to create an Agent and environment, start a session, and send a message. Save the following code as
Clients can be reused across threads. Close the resource-owning client when the application has finished using it; short programs can use
Explicit builder configuration takes precedence over environment variables. Default PAT mode reads
Use
Close the copy independently. Explicitly injected Transport and Credential objects remain owned by the caller; release these shared resources after all users have finished.
Pass a personal access token explicitly with
After setting
Synchronous methods return results directly.
You can also create an independent asynchronous client:
The example uses
Keep the resource-owning client open until requests complete. Interrupting a synchronous call cancels the request, preserves the thread interrupt flag, and throws
Request parameters are constructed through builders and become immutable afterward. Unset fields and fields explicitly set to
Whether a field accepts
Consume a single SSE connection through
Replace
Asynchronous
By default,
Ordinary request failures inherit from
Connection failures use
By default, requests are retried up to 2 times, for a maximum of 3 attempts. The SDK checks the request method, idempotency key, and body replayability before deciding whether to retry:
The SDK handles server retry hints and delays within these rules; some APIs impose stricter limits. Change the count through the client builder or per-call
The default connection timeout is 5 seconds, and response header wait and read/write idle timeouts are 10 minutes. Use
List responses support current-page access and on-demand iteration across pages:
Read
Use a resource
JSON data in
Interceptors can observe each HTTP attempt, including retries and SSE reconnections:
Interceptors should return promptly and call
Installation
Requires Java 8 or later.
Maven
Add the following to pom.xml:
Gradle
Use Maven Central and add a dependency with a pinned version:
Basic usage
Save your personal access token (PAT) in an environment variable:
ManagedQuickStart.java and run it in a project with the SDK dependency added:
try-with-resources for automatic closure. See the Managed quickstart and Forward API reference for more integration instructions.
Client configuration
| Configuration | Forward | Managed |
|---|---|---|
| Synchronous client | ForwardClient | ManagedClient |
| Asynchronous client | AsyncForwardClient | AsyncManagedClient |
| Base URL environment variable | QODER_FORWARD_BASE_URL | QODER_BASE_URL |
| Global default URL | https://api.qoder.com/api/v1/forward | https://api.qoder.com/api/v1/cloud |
| CN URL | https://api.qoder.com.cn/api/v1/forward | https://api.qoder.com.cn/api/v1/cloud |
QODER_ACCESS_TOKEN first, then QODER_PAT. Clients do not automatically load .env files. Choose a full Base URL matching the region of the account, credentials, and resources.
withOptions(...) to create a client copy with different settings. The original client keeps its configuration:
Authentication
PAT
Pass a personal access token explicitly with .pat(token), or configure it through environment variables. Store credentials in the application server environment.
Service Account
After setting QODER_SA_KEY, explicitly create a credential component. It exchanges and caches SATs (service account tokens), refreshing them when subsequent requests require it:
ManagedClient accepts the same credential component. Multiple clients can share a credential instance; the example closes the client before closing the credential. Closing a client does not close externally supplied credentials for the application.
For the China site, use both Region.CN and a CN Base URL. For Managed-only access, you can request the qca.access scope; permissions and workspace bindings must be configured on the server.
Ordinary clients do not automatically enable token exchange merely because QODER_SA_KEY exists in the environment. An SA Key cannot serve directly as a business API access token. SA authentication failures do not fall back to PAT.
If the application manages SATs itself, use .authMode(AuthMode.SA).accessToken(token), where AuthMode is in com.qoder.cloudagents.auth. The application must refresh static SATs. Configure only one explicit credential source per client. See the authentication documentation.
Synchronous and asynchronous calls
Synchronous methods return results directly. client.async() provides an asynchronous view sharing authentication and transport resources; ordinary asynchronous requests return CompletableFuture<T>:
join() to make a short program wait for request completion. Server-side asynchronous pipelines can compose Futures; call join() or get() only on threads that need to wait for results. Asynchronous failures are usually wrapped in CompletionException or ExecutionException; inspect the underlying SDK exception with getCause().
| Object | Close and cancellation behavior |
|---|---|
View returned by client.async() | Shares resources with the parent client; closing the view does not close the parent |
Client returned by buildAsync() | Owns resources independently; close it after use |
| Future returned by the SDK | cancel(true) cancels the corresponding HTTP call and retry waits |
Derived Future created by the application with thenApply or similar methods | Follows Java Future behavior; cancel the original SDK Future to stop the underlying request |
ApiUserAbortException.
Request and response types
Request parameters are constructed through builders and become immutable afterward. Unset fields and fields explicitly set to null are encoded differently:
| Builder behavior | JSON behavior |
|---|---|
| Do not call the field setter | Omit the field |
Set a field to null | Send null |
| Set an empty string or collection | Send "", [], or {} |
omit("field JSON name") | Remove a previously set field |
null and how that value is handled are defined by the corresponding API. For parameters accepting multiple JSON representations, use public overloads or wrapper types rather than relying on internal serialization.
Responses use immutable models with methods for reading fields. field(name) and rawJson() preserve access to the original JSON, and unknown fields are retained. Generic JSON values use com.qoder.cloudagents.core.JsonValue; applications do not need to depend on internal Jackson types.
Streaming responses
Consume a single SSE connection through EventStream<T> and release it with try-with-resources:
streamEvents ends at EOF and reports read or protocol errors to the caller. To keep subscribing after a recoverable disconnection, explicitly use resumableStream:
evt_checkpoint with a valid resume position; omit lastEventId for an initial subscription. A resumable stream connects when consumption begins, then uses the SSE cursor of the most recently delivered event. Calling only the iterator hasNext() does not advance the delivery cursor.
Without a cursor, the stream starts at the current event stream tail; reading history requires a valid historical resume position. Resumption does not resend business requests or guarantee exactly-once processing; multiple deltas for one event may share an ID.
maxRetries controls retries for each HTTP connection attempt; totalTimeout controls the entire subscription, including reconnection waits and reads. Evaluate stream termination and business task completion separately; see the API definitions for event meanings.
Asynchronous subscriptions
Asynchronous streamEvents(...) returns a Future for establishing the connection; the resulting EventStream still uses blocking reads. To read in the background and process callbacks sequentially, use subscribe:
subscribe uses its own reader thread, and it also accepts an application-provided Executor. Reads and callbacks execute in order; callbacks should return promptly. Do not schedule blocking reads on an application event-loop thread.
Closing the stream, closing the subscription, or cancelling its completion() ends reading and reconnection. These actions stop only the local subscription; they do not automatically stop execution already started in the cloud.
Error handling and Request ID
Ordinary request failures inherit from ApiException. HTTP errors provide status codes and request identifiers through ApiStatusException:
ApiConnectionException, with ApiTimeoutException as a subclass. SSE protocol or error frames may use StreamException; SA credential exchange failures use ServiceAccountExchangeException. Do not assume every failure has a business API HTTP status code.
Normal response models provide requestId(). Streams expose the current connection status code, response headers, and Request ID through response(). For a lazy resumable stream that has not connected yet, response() may be null.
Retries and idempotency
By default, requests are retried up to 2 times, for a maximum of 3 attempts. The SDK checks the request method, idempotency key, and body replayability before deciding whether to retry:
| Request | Retried by default for |
|---|---|
| GET, HEAD, or requests with an idempotency key | Connection errors, timeouts, 408, 429, 5xx |
| Other requests without an idempotency key | 429 |
| 409 response | No automatic retries |
| Non-replayable request body | No automatic retries |
RequestOptions.builder().maxRetries(...).build(). Set it to 0 to disable retries.
For write requests, configure the idempotency key with RequestOptions.builder().idempotencyKey(key). Reuse the same key for all retries of one logical operation; use a new key for a new operation.
Timeouts and cancellation
The default connection timeout is 5 seconds, and response header wait and read/write idle timeouts are 10 minutes. Use RequestOptions to distinguish per-attempt limits from a total deadline:
timeout adjusts the response header wait and read/write idle limits for each attempt. totalTimeout covers credential acquisition, all attempts, retry waits, and response body reads. By default, there is no total deadline for the entire call.
After SSE response headers are accepted, the ordinary read-idle timer is not enabled. Set totalTimeout to limit subscription duration. Reaching a deadline or cancelling a local request does not mean that execution already started in the cloud is cancelled.
Pagination
List responses support current-page access and on-demand iteration across pages:
page.data() to process only the current page. For manual pagination, check hasNextPage() before calling getNextPage(); use getNextPageAsync() for asynchronous pagination. Pagination preserves query conditions and request configuration, so you do not need to construct cursor parameters manually.
Advanced configuration
Raw response information
Use a resource withResponse() to obtain the parsed result, HTTP status, and response headers together:
ApiResponse is already parsed. Do not treat it as an unconsumed raw HTTP response body.
HTTP interceptors and Transport
Interceptors can observe each HTTP attempt, including retries and SSE reconnections:
chain.proceed(...) at most once per invocation. Completion indicates response header receipt, not completion of SSE or response body consumption. Do not read streaming bodies early just to log them.
To configure your own HTTP library, proxy, TLS, or connection pool, implement the public HttpTransport interface and inject it through .transport(...). Transport handles a single transmission and cancellation; do not enable its own automatic retries on top of SDK retries. The application owns explicitly injected Transport instances and manages their lifecycle.
For individual calls, set additional request parameters with header(...) and query(...) on RequestOptions.builder(), then call build(). JSON fields already supported by the server but not yet modeled by the SDK can be supplied through public JSON model interfaces. Prefer typed methods for ordinary calls.