Retry
BerryCrush provides built-in retry support for handling transient failures in HTTP requests. The retry mechanism automatically retries failed requests based on configurable conditions.
Overview
When enabled, the retry mechanism:
Automatically retries requests that fail with retryable status codes
Handles transient exceptions like connection timeouts
Supports multiple backoff strategies (fixed, linear, exponential)
Adds jitter to prevent thundering herd problems
Provides detailed exception information when all retries are exhausted
Configuration
Programmatic Configuration
Configure retry behavior using the retry { } DSL block in your test
configuration:
@BerryCrushConfiguration
class PetApiTest {
companion object {
@JvmStatic
@BeforeAll
fun setup() {
BerryCrush.configure {
spec("petstore.yaml")
retry {
maxAttempts = 3
delay = Duration.ofSeconds(1)
maxDelay = Duration.ofSeconds(30)
backoff = BackoffStrategy.EXPONENTIAL
jitter = true
retryOnStatusCodes = setOf(429, 502, 503, 504)
}
}
}
}
}
Java equivalent:
@BerryCrushConfiguration
public class PetApiTest {
@BeforeAll
static void setup() {
BerryCrush.configure(config -> {
config.spec("petstore.yaml");
config.retry(retry -> {
retry.setMaxAttempts(3);
retry.setDelay(Duration.ofSeconds(1));
retry.setMaxDelay(Duration.ofSeconds(30));
retry.setBackoff(BackoffStrategy.EXPONENTIAL);
retry.setJitter(true);
retry.setRetryOnStatusCodes(Set.of(429, 502, 503, 504));
});
});
}
}
Parameter-Based Configuration
You can also configure retry via file-level or scenario-level parameters.
File-level parameters (apply to all scenarios in the file):
parameters:
retry.maxAttempts: 3
retry.delay: "1s"
retry.maxDelay: "30s"
retry.backoff: exponential
retry.jitter: true
scenario: Pet API with retry
when: I request pets
call ^listPets
then: I receive a response
assert status 200
Nested syntax (alternative, more readable format):
parameters:
retry:
maxAttempts: 3
delay: "1s"
maxDelay: "30s"
backoff: exponential
jitter: true
Scenario-level parameters (apply to a specific scenario):
scenario: Flaky endpoint with retry
parameters:
retry:
maxAttempts: 5
delay: "500ms"
backoff: exponential
when: I call flaky endpoint
call ^flakyOperation
then: eventually succeeds
assert status 200
Configuration Options
Option |
Default |
Description |
|---|---|---|
|
0 (disabled) |
Maximum retry attempts. Set to 0 to disable retries. |
|
1 second |
Initial delay between retry attempts. |
|
30 seconds |
Maximum delay cap (for exponential/linear backoff). |
|
EXPONENTIAL |
Backoff strategy: FIXED, LINEAR, or EXPONENTIAL. |
|
true |
Add ±20% randomization to delay (prevents thundering herd). |
|
429, 502, 503, 504 |
HTTP status codes that trigger retry. |
|
See below |
Exception types that trigger retry. |
Backoff Strategies
BerryCrush supports three backoff strategies:
FIXED
Same delay for every retry attempt.
Attempt 1: wait 1s
Attempt 2: wait 1s
Attempt 3: wait 1s
LINEAR
Delay increases linearly with each attempt.
Attempt 1: wait 1s (1s × 1)
Attempt 2: wait 2s (1s × 2)
Attempt 3: wait 3s (1s × 3)
EXPONENTIAL
Delay doubles with each attempt (recommended for production).
Attempt 1: wait 1s (1s × 2^0)
Attempt 2: wait 2s (1s × 2^1)
Attempt 3: wait 4s (1s × 2^2)
Default Retry Conditions
Status Codes
By default, the following HTTP status codes trigger retry:
429 Too Many Requests - Rate limiting
502 Bad Gateway - Server error
503 Service Unavailable - Server temporarily unavailable
504 Gateway Timeout - Server timeout
Exceptions
The following exception types trigger retry by default:
java.net.SocketTimeoutExceptionjava.net.ConnectExceptionjava.net.SocketException
Custom Retry Conditions
Configure custom status codes:
retry {
retryOnStatusCodes = setOf(429, 500, 502, 503, 504)
}
Configure custom exception types:
retry {
retryOnExceptions = setOf(
SocketTimeoutException::class,
ConnectException::class,
SSLException::class,
)
}
Error Handling
When all retry attempts are exhausted, a RetryExhaustedException is thrown
containing:
attempts- Total number of attempts madelastResponse- The last HTTP response (if status code failure)lastException- The last exception (if exception failure)
Example handling:
try {
runScenario("my-scenario")
} catch (e: RetryExhaustedException) {
println("Failed after ${e.attempts} attempts")
e.lastResponse?.let { println("Last status: ${it.statusCode()}") }
e.lastException?.let { println("Last error: ${it.message}") }
}
Jitter
When jitter is enabled (default), the retry mechanism adds ±20% randomization
to the calculated delay. This prevents the “thundering herd” problem where many
clients retry simultaneously after a server recovers.
For example, with exponential backoff and a 4-second calculated delay:
Without jitter: wait exactly 4.0s
With jitter: wait 3.2s - 4.8s (random within ±20%)
Best Practices
Enable for external APIs: Always enable retry for third-party APIs that may experience transient failures.
Use exponential backoff: Preferred for production environments to avoid overwhelming recovering servers.
Keep jitter enabled: Prevents synchronized retry storms.
Set reasonable maxDelay: Cap the maximum delay to prevent excessively long waits (default 30s is usually appropriate).
Include 429 status code: Rate limiting is the most common retryable error.
Don’t retry on 4xx errors: Client errors (except 429) typically indicate bugs in the request, not transient issues.
Example: Full Configuration
BerryCrush.configure {
spec("api.yaml")
baseUrl("https://api.example.com")
// Enable retry with exponential backoff
retry {
maxAttempts = 3
delay = Duration.ofMillis(500)
maxDelay = Duration.ofSeconds(10)
backoff = BackoffStrategy.EXPONENTIAL
jitter = true
retryOnStatusCodes = setOf(429, 502, 503, 504)
retryOnExceptions = setOf(
SocketTimeoutException::class,
ConnectException::class,
)
}
}
See Also
Enhanced Error Context - Error context and reporting
File-Level Parameters - Parameter configuration
Kotlin DSL - Kotlin DSL reference