Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codegen.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{ "engineHash": "bfb97cc", "specHash": "77eac4b", "version": "5.4.0" }
{ "engineHash": "bc04b80", "specHash": "f2523d5", "version": "5.4.0" }
4 changes: 4 additions & 0 deletions docs/sdkgen/archives.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ Permanently deletes an archive.

To learn more about the archive APIs, see the [Archive API Guide](https://developer.box.com/guides/archives).

<Danger>
This endpoint is currently unavailable. Please contact support for assistance.
</Danger>

This operation is performed by calling function `deleteArchiveByIdV2025R0`.

See the endpoint docs at
Expand Down
13 changes: 13 additions & 0 deletions docs/sdkgen/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ divided across resource managers.
- [Custom headers](#custom-headers)
- [Custom Base URLs](#custom-base-urls)
- [Interceptors](#interceptors)
- [Use Timeouts for API calls](#use-timeouts-for-api-calls)
- [Use Proxy for API calls](#use-proxy-for-api-calls)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Expand Down Expand Up @@ -178,6 +179,18 @@ List<Interceptor> interceptors = new ArrayList<>() {
BoxClient clientWithInterceptor = client.withInterceptors(interceptors);
```

# Use Timeouts for API calls

In order to configure timeout for API calls, calling the `client.withTimeouts(config)` method creates a new client with timeout settings, leaving the original client unmodified.

```java
TimeoutConfig timeoutConfig = new TimeoutConfig.Builder()
.connectionTimeoutMs(10000L)
.readTimeoutMs(30000L)
.build();
BoxClient newClient = client.withTimeouts(timeoutConfig);
```

# Use Proxy for API calls

In order to use a proxy for API calls, calling the `client.withProxy(proxyConfig)` method creates a new client, leaving the original client unmodified, with the username and password being optional. We only support adding proxy for BoxNetworkClient. If you are using your own implementation of NetworkClient, you would need to configure proxy on your own.
Expand Down
178 changes: 167 additions & 11 deletions docs/sdkgen/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,163 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Configuration](#configuration)
- [Max retry attempts](#max-retry-attempts)
- [Custom retry strategy](#custom-retry-strategy)
- [Retry Strategy](#retry-strategy)
- [Overview](#overview)
- [Default Configuration](#default-configuration)
- [Retry Decision Flow](#retry-decision-flow)
- [Exponential Backoff Algorithm](#exponential-backoff-algorithm)
- [Example Delays (with default settings)](#example-delays-with-default-settings)
- [Retry-After Header](#retry-after-header)
- [Network Exception Handling](#network-exception-handling)
- [Customizing Retry Parameters](#customizing-retry-parameters)
- [Custom Retry Strategy](#custom-retry-strategy)
- [Timeouts](#timeouts)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Max retry attempts
## Retry Strategy

The default maximum number of retries in case of failed API call is 5.
To change this number you should initialize `BoxRetryStrategy` with the new value and pass it to `NetworkSession`.
### Overview

The SDK ships with a built-in retry strategy (`BoxRetryStrategy`) that implements the `RetryStrategy` interface. The `BoxNetworkClient`, which serves as the default network client, uses this strategy to automatically retry failed API requests with exponential backoff.

The retry strategy exposes two methods:

- **`shouldRetry`** — Determines whether a failed request should be retried based on the HTTP status code, response headers, attempt count, and authentication state.
- **`retryAfter`** — Computes the delay (in seconds) before the next retry attempt, using either the server-provided `Retry-After` header or an exponential backoff formula.

### Default Configuration

| Parameter | Default | Description |
| -------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `maxAttempts` | `5` | Maximum number of retry attempts for HTTP error responses (status 4xx/5xx). |
| `retryBaseInterval` | `1` (second) | Base interval used in the exponential backoff calculation. |
| `retryRandomizationFactor` | `0.5` | Jitter factor applied to the backoff delay. The actual delay is multiplied by a random value between `1 - factor` and `1 + factor`. |
| `maxRetriesOnException` | `2` | Maximum number of retries for network-level exceptions (connection failures, timeouts). These are tracked by a separate counter from HTTP error retries. |

### Retry Decision Flow

The following diagram shows how `BoxRetryStrategy.shouldRetry` decides whether to retry a request:

```
shouldRetry(fetchOptions, fetchResponse, attemptNumber)
|
v
+-----------------------+
| status == 0 | Yes
| (network exception)? |----------> attemptNumber <= maxRetriesOnException?
+-----------------------+ | |
| No Yes No
v | |
+-----------------------+ [RETRY] [NO RETRY]
| attemptNumber >= |
| maxAttempts? |
+-----------------------+
| |
Yes No
| |
[NO RETRY] v
+-----------------------+
| status == 202 AND | Yes
| Retry-After header? |----------> [RETRY]
+-----------------------+
| No
v
+-----------------------+
| status >= 500 | Yes
| (server error)? |----------> [RETRY]
+-----------------------+
| No
v
+-----------------------+
| status == 429 | Yes
| (rate limited)? |----------> [RETRY]
+-----------------------+
| No
v
+-----------------------+
| status == 401 AND | Yes
| auth available? |----------> Refresh token, then [RETRY]
+-----------------------+
| No
v
[NO RETRY]
```

### Exponential Backoff Algorithm

When the response does not include a `Retry-After` header, the retry delay is computed using exponential backoff with randomized jitter:

```
delay = 2^attemptNumber * retryBaseInterval * random(1 - factor, 1 + factor)
```

Where:

- `attemptNumber` is the current attempt (1-based)
- `retryBaseInterval` defaults to `1` second
- `factor` is `retryRandomizationFactor` (default `0.5`)
- `random(min, max)` returns a uniformly distributed value in `[min, max]`

#### Example Delays (with default settings)

| Attempt | Base Delay | Min Delay (factor=0.5) | Max Delay (factor=0.5) |
| ------- | ---------- | ---------------------- | ---------------------- |
| 1 | 2s | 1.0s | 3.0s |
| 2 | 4s | 2.0s | 6.0s |
| 3 | 8s | 4.0s | 12.0s |
| 4 | 16s | 8.0s | 24.0s |

### Retry-After Header

When the server includes a `Retry-After` header in the response, the SDK uses the header value directly as the delay in seconds instead of computing an exponential backoff delay. This applies to any retryable response that includes the header, including:

- `202 Accepted` with `Retry-After` (long-running operations)
- `429 Too Many Requests` with `Retry-After`
- `5xx` server errors with `Retry-After`

The header value is parsed as a floating-point number representing seconds.

### Network Exception Handling

Network-level failures (connection refused, DNS resolution errors, timeouts, TLS errors) are represented internally as responses with status `0`. These exceptions are tracked by a **separate counter** (`maxRetriesOnException`, default `2`) from the regular HTTP error retry counter (`maxAttempts`).

This means:

- Network exception retries are tracked independently from HTTP error retries, each with their own counter and backoff progression.
- A request can fail up to `maxRetriesOnException` times due to network exceptions, but each exception retry also increments the overall attempt counter, so the total number of retries across both exception and HTTP error types is bounded by `maxAttempts`.

### Customizing Retry Parameters

You can customize all retry parameters by initializing `BoxRetryStrategy` with the desired values and passing it to `NetworkSession`:

```java
BoxDeveloperTokenAuth auth = new BoxDeveloperTokenAuth("DEVELOPER_TOKEN");
NetworkSession session = new NetworkSession.Builder()
.retryStrategy(new BoxRetryStrategy.Builder().maxAttempts(3).build())
.retryStrategy(
new BoxRetryStrategy.Builder()
.maxAttempts(3)
.retryBaseInterval(2)
.retryRandomizationFactor(0.3)
.maxRetriesOnException(1)
.build()
)
.build();
BoxClient client = new BoxClient.Builder(auth)
.networkSession(session)
.build();
```

## Custom retry strategy
### Custom Retry Strategy

You can also implement your own retry strategy by subclassing `RetryStrategy` and overriding `shouldRetry` and `retryAfter` methods.
This example shows how to set custom strategy that retries on 5xx status codes and waits 1 second between retries.
You can implement your own retry strategy by implementing the `RetryStrategy` interface and overriding the `shouldRetry` and `retryAfter` methods:

```java
BoxDeveloperTokenAuth auth = new BoxDeveloperTokenAuth("DEVELOPER_TOKEN");
RetryStrategy customRetryStrategy = new RetryStrategy() {
@Override
public boolean shouldRetry(FetchOptions fetchOptions, FetchResponse fetchResponse, int attemptNumber) {
return fetchResponse.status >= 500;
return fetchResponse.getStatus() >= 500 && attemptNumber < 3;
}

@Override
Expand All @@ -49,3 +174,34 @@ BoxClient client = new BoxClient.Builder(auth)
.networkSession(session)
.build();
```

## Timeouts

You can configure network timeouts with `TimeoutConfig` on `NetworkSession`.
Java SDK supports separate values for connection and read timeouts, both in milliseconds.

```java
BoxDeveloperTokenAuth auth = new BoxDeveloperTokenAuth("DEVELOPER_TOKEN");
TimeoutConfig timeoutConfig = new TimeoutConfig.Builder()
.connectionTimeoutMs(10000L)
.readTimeoutMs(30000L)
.build();

NetworkSession session = new NetworkSession()
.withTimeoutConfig(timeoutConfig);

BoxClient client = new BoxClient.Builder(auth)
.networkSession(session)
.build();
```

How timeout handling works:

- `connectionTimeoutMs` controls how long the client waits to establish a connection.
- `readTimeoutMs` controls how long the client waits for data while reading the response.
- Each timeout is optional. If a value is not provided, the client keeps its existing timeout for that setting.
- To disable both timeouts, set `connectionTimeoutMs(0L)` and `readTimeoutMs(0L)`.
- You can also disable only one timeout by setting just one of them to `0L` and leaving the other configured.
- Timeout failures are handled as request exceptions, then retry behavior is controlled by the configured retry strategy
- If retries are exhausted after timeout failures, the SDK throws `BoxSDKError` with the underlying timeout exception as the cause.
- Timeout applies to a single HTTP request attempt to the Box API (not the total time across all retries).
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ public void testArchivesCreateListDelete() {
.getArchives()
.getArchivesV2025R0(new GetArchivesV2025R0QueryParams.Builder().limit(100L).build());
assert archives.getEntries().size() > 0;
client.getArchives().deleteArchiveByIdV2025R0(archive.getId());
assertThrows(
RuntimeException.class,
() -> client.getArchives().deleteArchiveByIdV2025R0(archive.getId()));
Expand Down
17 changes: 17 additions & 0 deletions src/intTest/java/com/box/sdkgen/client/ClientITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.box.sdkgen.networking.fetchoptions.MultipartItem;
import com.box.sdkgen.networking.fetchoptions.ResponseFormat;
import com.box.sdkgen.networking.fetchresponse.FetchResponse;
import com.box.sdkgen.networking.timeoutconfig.TimeoutConfig;
import com.box.sdkgen.schemas.filefull.FileFull;
import com.box.sdkgen.schemas.files.Files;
import com.box.sdkgen.schemas.folderfull.FolderFull;
Expand Down Expand Up @@ -199,6 +200,22 @@ public void testWithCustomBaseUrls() {
assertThrows(RuntimeException.class, () -> customBaseClient.getUsers().getUserMe());
}

@Test
public void testWithTimeoutWhenTimeoutOccurs() {
long readTimeoutMs = 1;
BoxClient clientWithTimeout =
client.withTimeouts(new TimeoutConfig.Builder().readTimeoutMs(readTimeoutMs).build());
assertThrows(RuntimeException.class, () -> clientWithTimeout.getUsers().getUserMe());
}

@Test
public void testWithTimeoutWhenTimeoutDoesNotOccur() {
long readTimeoutMs = 10000;
BoxClient clientWithTimeout =
client.withTimeouts(new TimeoutConfig.Builder().readTimeoutMs(readTimeoutMs).build());
clientWithTimeout.getUsers().getUserMe();
}

@Test
public void testWithInterceptors() {
UserFull user = client.getUsers().getUserMe();
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/box/sdkgen/client/BoxClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
import com.box.sdkgen.networking.interceptors.Interceptor;
import com.box.sdkgen.networking.network.NetworkSession;
import com.box.sdkgen.networking.proxyconfig.ProxyConfig;
import com.box.sdkgen.networking.timeoutconfig.TimeoutConfig;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -1046,6 +1047,17 @@ public BoxClient withProxy(ProxyConfig config) {
.build();
}

/**
* Create a new client with custom timeouts that will be used for every API call
*
* @param config Timeout configuration.
*/
public BoxClient withTimeouts(TimeoutConfig config) {
return new BoxClient.Builder(this.auth)
.networkSession(this.networkSession.withTimeoutConfig(config))
.build();
}

/**
* Create a new client with a custom set of interceptors that will be used for every API call
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ public ArchiveV2025R0 createArchiveV2025R0(
* <p>To learn more about the archive APIs, see the [Archive API
* Guide](https://developer.box.com/guides/archives).
*
* <p>&lt;Danger&gt; This endpoint is currently unavailable. Please contact support for
* assistance. &lt;/Danger&gt;
*
* @param archiveId The ID of the archive. Example: "982312"
*/
public void deleteArchiveByIdV2025R0(String archiveId) {
Expand All @@ -167,6 +170,9 @@ public void deleteArchiveByIdV2025R0(String archiveId) {
* <p>To learn more about the archive APIs, see the [Archive API
* Guide](https://developer.box.com/guides/archives).
*
* <p>&lt;Danger&gt; This endpoint is currently unavailable. Please contact support for
* assistance. &lt;/Danger&gt;
*
* @param archiveId The ID of the archive. Example: "982312"
* @param headers Headers of deleteArchiveByIdV2025R0 method
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.box.sdkgen.networking.network.NetworkSession;
import com.box.sdkgen.networking.networkclient.NetworkClient;
import com.box.sdkgen.networking.proxyconfig.ProxyConfig;
import com.box.sdkgen.networking.timeoutconfig.TimeoutConfig;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.IOException;
import java.net.InetSocketAddress;
Expand Down Expand Up @@ -97,6 +98,31 @@ public BoxNetworkClient withProxy(ProxyConfig config) {
return new BoxNetworkClient(clientBuilder.build());
}

public BoxNetworkClient withTimeoutConfig(TimeoutConfig config) {
if (config == null) {
throw new IllegalArgumentException("TimeoutConfig cannot be null");
}

OkHttpClient.Builder clientBuilder = httpClient.newBuilder();

Long connectionTimeoutMs = config.getConnectionTimeoutMs();
if (connectionTimeoutMs != null) {
if (connectionTimeoutMs < 0) {
throw new IllegalArgumentException("connectionTimeoutMs cannot be negative");
}
clientBuilder.connectTimeout(connectionTimeoutMs.longValue(), TimeUnit.MILLISECONDS);
}

Long readTimeoutMs = config.getReadTimeoutMs();
if (readTimeoutMs != null) {
if (readTimeoutMs < 0) {
throw new IllegalArgumentException("readTimeoutMs cannot be negative");
}
clientBuilder.readTimeout(readTimeoutMs.longValue(), TimeUnit.MILLISECONDS);
}
return new BoxNetworkClient(clientBuilder.build());
}

public FetchResponse fetch(FetchOptions options) {
NetworkSession networkSession =
options.getNetworkSession() == null ? new NetworkSession() : options.getNetworkSession();
Expand Down
Loading