From 3d12c07f5d35049ad5870bac87bacc6abeae15fe Mon Sep 17 00:00:00 2001 From: box-sdk-build Date: Mon, 16 Feb 2026 02:44:55 -0800 Subject: [PATCH 1/3] test: assume that exception is thrown from delete archive (box/box-codegen#927) --- .codegen.json | 2 +- src/intTest/java/com/box/sdkgen/archives/ArchivesITest.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.codegen.json b/.codegen.json index 438e56798..1304796b9 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "bfb97cc", "specHash": "77eac4b", "version": "5.4.0" } +{ "engineHash": "f36ed52", "specHash": "77eac4b", "version": "5.4.0" } diff --git a/src/intTest/java/com/box/sdkgen/archives/ArchivesITest.java b/src/intTest/java/com/box/sdkgen/archives/ArchivesITest.java index d02b09b34..03bc91d7d 100644 --- a/src/intTest/java/com/box/sdkgen/archives/ArchivesITest.java +++ b/src/intTest/java/com/box/sdkgen/archives/ArchivesITest.java @@ -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())); From e46acf653dd5a4c4d8c8634be38ad467d1180d4a Mon Sep 17 00:00:00 2001 From: box-sdk-build Date: Mon, 16 Feb 2026 06:44:52 -0800 Subject: [PATCH 2/3] chore: Update `.codegen.json` with commit hash of `codegen` and `openapi` spec [skip ci] --- .codegen.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codegen.json b/.codegen.json index 1304796b9..cc7c86d5c 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "f36ed52", "specHash": "77eac4b", "version": "5.4.0" } +{ "engineHash": "9dcb945", "specHash": "77eac4b", "version": "5.4.0" } From c3062ddb0bd97ed153ecded02b88d511d4345a73 Mon Sep 17 00:00:00 2001 From: box-sdk-build Date: Mon, 16 Feb 2026 09:44:53 -0800 Subject: [PATCH 3/3] docs: Improve documentation for retry strategies (box/box-codegen#925) --- .codegen.json | 2 +- docs/sdkgen/configuration.md | 146 ++++++++++++++++++++++++++++++++--- 2 files changed, 136 insertions(+), 12 deletions(-) diff --git a/.codegen.json b/.codegen.json index cc7c86d5c..2a5c34fb3 100644 --- a/.codegen.json +++ b/.codegen.json @@ -1 +1 @@ -{ "engineHash": "9dcb945", "specHash": "77eac4b", "version": "5.4.0" } +{ "engineHash": "482939a", "specHash": "77eac4b", "version": "5.4.0" } diff --git a/docs/sdkgen/configuration.md b/docs/sdkgen/configuration.md index ab18f97a8..8b49875b1 100644 --- a/docs/sdkgen/configuration.md +++ b/docs/sdkgen/configuration.md @@ -3,38 +3,162 @@ -- [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) -## 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