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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
- 2.1.0
- 2.1.1
- 2.1.2
- 2.2.0
- latest
os:
- ubuntu-latest
Expand Down
36 changes: 33 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,16 +326,31 @@ Vault's HTTP API:
credentials).
* `debug()`: Health check endpoints.

The driver DSL also allows you to specify retry logic, by chaining the `withRetries()` ahead of
accessing the endpoint implementation:
By default, API operations are not retried. To retry every call, set the retries in the
configuration:

```
// Retry up to 5 times if failures occur, waiting 1000 milliseconds in between each retry attempt.
final LogicalResponse response = vault.withRetries(5, 1000)
final VaultConfig config = new VaultConfig()
.address("http://127.0.0.1:8200")
.token("3c9fd6be-7bc2-9d1f-6fb3-cd746c0fc4e8")
.retries(5, 1000)
.build();
```

To retry only some calls, chain `retrying()` ahead of accessing the endpoint implementation. It
returns a new `Vault` sharing the same configuration, and leaves the original `vault` unchanged:

```
final LogicalResponse response = vault.retrying(5, 1000)
.logical()
.read("secret/hello");
```

`withRetries()` is deprecated: despite being chained like `retrying()`, it changes the retries in the
configuration, so they apply to every later call, and to every `Vault` sharing that configuration.
Replace it with `retrying()` or `VaultConfig.retries()`.

Only transient failures are retried: I/O errors (e.g. connection refused or timed out) and HTTP 5xx,
408, 412 and 429 responses. Other 4xx responses (e.g. 400, 403, 404) fail immediately, since
retrying them would not help.
Expand All @@ -356,6 +371,10 @@ patches.
* Security: fixes [CVE-2026-109408](https://www.cve.org/CVERecord?id=CVE-2026-109408), where SSL
certificate verification could be silently disabled. See the `VAULT_SSL_VERIFY` and `SslConfig`
entries below
* Security: a deeply nested JSON value stored in Vault no longer kills the reading thread with an
uncaught `StackOverflowError`. Responses nested deeper than 500 levels are now ignored like
other malformed responses (Vault 1.20 and later reject values nested deeper than 298 levels).
The vendored JSON parser also rejects documents nested deeper than 1000 levels
* Environment variables: surrounding whitespace is removed, and empty values are treated as unset
* `VAULT_SSL_VERIFY` now only disables SSL verification when set to `false`. Previously any value
other than `true` disabled it, including `1`, `yes`, typos and empty values. **Values like `0`,
Expand Down Expand Up @@ -407,6 +426,17 @@ patches.
* Javadoc examples use the `Sys` methods instead of the deprecated ones. The `Wrapping` examples
are fixed: they used `new Vault(config)` instead of `Vault.create(config)`, and a misspelled
`lookupWarp()` method
* Retries can be limited to some calls [(issue #75)](https://github.com/jopenlibs/vault-java-driver/issues/75).
`Vault.retrying(maxRetries, retryIntervalMilliseconds)` returns a new `Vault` whose operations
use those retries, leaving the original instance and its configuration unchanged.
`VaultConfig.retries(maxRetries, retryIntervalMilliseconds)` sets retries for every call.
Both reject negative values with `IllegalArgumentException`. The operation classes (e.g.
`Logical`, `Sys`, `Pki`) have new constructors taking a `RetryPolicy`
* `Vault.withRetries()` is deprecated, with unchanged behaviour: it writes the retries to the
shared `VaultConfig`, so they apply to every later call and to every `Vault` sharing that
configuration, not only the chained call. Use `retrying()` or `VaultConfig.retries()` instead.
`Vault.retrying()` is a `default` method throwing `UnsupportedOperationException`, so custom
implementations of `Vault` keep compiling
* **6.2.3**: This release contains the following updates:
* Improved Java 11 compatibility
* Fixed integration tests with Vault 2.x
Expand Down
73 changes: 73 additions & 0 deletions src/main/java/io/github/jopenlibs/vault/RetryPolicy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.github.jopenlibs.vault;

/**
* <p>An immutable retry setting for API operations: the maximum number of times a failing
* operation is retried, and the time to wait between attempts.</p>
*
* <p>A policy with <code>maxRetries</code> set to <code>0</code> disables retries.</p>
*
* @see Vault#retrying(int, int)
* @see VaultConfig#retries(int, int)
*/
public final class RetryPolicy {

private final int maxRetries;
private final int retryIntervalMilliseconds;

/**
* Creates a retry policy.
*
* @param maxRetries The number of times that a failing API operation will be retried
* @param retryIntervalMilliseconds The number of milliseconds to wait between retries
* @throws IllegalArgumentException If either value is negative
*/
public RetryPolicy(final int maxRetries, final int retryIntervalMilliseconds) {
if (maxRetries < 0) {
throw new IllegalArgumentException("maxRetries must not be negative: " + maxRetries);
}
if (retryIntervalMilliseconds < 0) {
throw new IllegalArgumentException(
"retryIntervalMilliseconds must not be negative: " + retryIntervalMilliseconds);
}
this.maxRetries = maxRetries;
this.retryIntervalMilliseconds = retryIntervalMilliseconds;
}

/**
* @return The number of times that a failing API operation will be retried
*/
public int getMaxRetries() {
return maxRetries;
}

/**
* @return The number of milliseconds to wait between retries
*/
public int getRetryIntervalMilliseconds() {
return retryIntervalMilliseconds;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof RetryPolicy)) {
return false;
}
final RetryPolicy other = (RetryPolicy) o;
return maxRetries == other.maxRetries
&& retryIntervalMilliseconds == other.retryIntervalMilliseconds;
}

@Override
public int hashCode() {
return 31 * maxRetries + retryIntervalMilliseconds;
}

@Override
public String toString() {
return "RetryPolicy{maxRetries=" + maxRetries
+ ", retryIntervalMilliseconds=" + retryIntervalMilliseconds + "}";
}
}
47 changes: 44 additions & 3 deletions src/main/java/io/github/jopenlibs/vault/Vault.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,58 @@ static Vault create(final VaultConfig vaultConfig, final Boolean useSecretsEngin
}

/**
* This method is chained ahead of endpoints (e.g. <code>logical()</code>, <code>auth()</code>,
* etc... to specify retry rules for any API operations invoked on that endpoint.
* <p>Sets the retry rules for API operations, by writing them to the configuration of this
* instance.</p>
*
* <p>Despite being chained ahead of endpoints (e.g. <code>logical()</code>,
* <code>auth()</code>), the retries are not limited to that call: they apply to every later
* call on this instance, and on every other <code>Vault</code> sharing the same configuration.
* A <code>Vault</code> returned by {@link #retrying(int, int)} ignores them.</p>
*
* @param maxRetries The number of times that API operations will be retried when a failure
* occurs
* @param retryIntervalMilliseconds The number of milliseconds that the driver will wait in
* between retries
* @return This object, with maxRetries and retryIntervalMilliseconds populated
* @return This object, with maxRetries and retryIntervalMilliseconds populated in its
* configuration
* @deprecated To retry only some calls, use {@link #retrying(int, int)}, which returns a new
* instance. To retry every call, use {@link VaultConfig#retries(int, int)}.
*/
@Deprecated
Vault withRetries(final int maxRetries, final int retryIntervalMilliseconds);

/**
* <p>Returns a new <code>Vault</code> whose API operations are retried with the given rules,
* overriding the retries set in the configuration. This instance, and its configuration, are
* left unchanged:</p>
*
* <blockquote>
* <pre>{@code
* final LogicalResponse response = vault.retrying(5, 1000).logical().read("secret/hello");
* }</pre>
* </blockquote>
*
* <p>The returned instance shares the configuration of this one, so later changes to it (e.g.
* a new token) apply to both.</p>
*
* <p>The default implementation throws <code>UnsupportedOperationException</code>, so that
* existing implementations of this interface keep compiling.</p>
*
* @param maxRetries The number of times that API operations will be retried when a failure
* occurs
* @param retryIntervalMilliseconds The number of milliseconds that the driver will wait in
* between retries
* @return A new <code>Vault</code> using the given retries
* @throws IllegalArgumentException If either value is negative
* @throws UnsupportedOperationException If this implementation does not support it
* @see RetryPolicy
* @see VaultConfig#retries(int, int)
*/
default Vault retrying(final int maxRetries, final int retryIntervalMilliseconds) {
throw new UnsupportedOperationException(
getClass().getName() + " does not support retrying()");
}

/**
* Returns the implementing class for Vault's core/logical operations (e.g. read, write).
*
Expand Down
34 changes: 30 additions & 4 deletions src/main/java/io/github/jopenlibs/vault/VaultConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,38 @@ public VaultConfig httpClient(HttpClient httpClient) {
return this;
}

/**
* <p>Sets the retry rules for every API operation using this configuration: the maximum number
* of times that a failing operation will be retried, and the time to wait between attempts.
* By default, operations are not retried.</p>
*
* <p>To retry only some calls, use <code>Vault.retrying()</code> instead, which overrides
* these values.</p>
*
* @param maxRetries The number of times that API operations will be retried when a failure
* occurs
* @param retryIntervalMilliseconds The number of milliseconds that the driver will wait in
* between retries
* @return This object, with maxRetries and retryIntervalMilliseconds populated, ready for
* additional builder-pattern method calls or else finalization with the build() method
* @throws IllegalArgumentException If either value is negative, in which case the previous
* values are kept
* @see Vault#retrying(int, int)
* @see RetryPolicy
*/
public VaultConfig retries(final int maxRetries, final int retryIntervalMilliseconds) {
final var policy = new RetryPolicy(maxRetries, retryIntervalMilliseconds);
this.maxRetries = policy.getMaxRetries();
this.retryIntervalMilliseconds = policy.getRetryIntervalMilliseconds();
return this;
}

/**
* <p>Sets the maximum number of times that an API operation will retry upon failure.</p>
*
* <p>This method is not meant to be called from application-level code outside of this package
* (hence the <code>protected</code> access level. It is meant to be invoked via
* <code>Vault.withRetries()</code> in a builder pattern DSL-style.</p>
* (hence the package-private access level). It is used by the deprecated
* <code>Vault.withRetries()</code>: use {@link #retries(int, int)} instead.</p>
*
* @param maxRetries The number of times that API operations will be retried when a failure
* occurs.
Expand All @@ -338,8 +364,8 @@ void setMaxRetries(final int maxRetries) {
* attempts for a failing API operation.</p>
*
* <p>This method is not meant to be called from application-level code outside of this package
* (hence the <code>protected</code> access level. It is meant to be invoked via
* <code>Vault.withRetries()</code> in a builder pattern DSL-style.</p>
* (hence the package-private access level). It is used by the deprecated
* <code>Vault.withRetries()</code>: use {@link #retries(int, int)} instead.</p>
*
* @param retryIntervalMilliseconds The number of milliseconds that the driver will wait in
* between retries.
Expand Down
Loading
Loading