From c535d37de9ae028e5c076aab32fc5c8cac9411a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Tue, 27 Jan 2026 18:10:28 +0100 Subject: [PATCH 1/4] feat: support both palyoads when polling --- .../repository/PollingFeatureFetcher.java | 22 +++++++++++++------ .../StreamingFeatureFetcherImpl.java | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/getunleash/repository/PollingFeatureFetcher.java b/src/main/java/io/getunleash/repository/PollingFeatureFetcher.java index a5c5eba2d..ccfee5f31 100644 --- a/src/main/java/io/getunleash/repository/PollingFeatureFetcher.java +++ b/src/main/java/io/getunleash/repository/PollingFeatureFetcher.java @@ -85,10 +85,13 @@ private Runnable runInitialFetch(final Consumer handler) { return () -> { try { ClientFeaturesResponse response = featureFetcher.fetchFeatures(); - eventEmitter.update(response); if (response.getStatus() == ClientFeaturesResponse.Status.CHANGED) { - updateFeatures(response); - } else if (response.getStatus() == ClientFeaturesResponse.Status.UNAVAILABLE) { + String currentState = applyClientFeatures(response); + eventEmitter.update(ClientFeaturesResponse.updated(currentState)); + } else { + eventEmitter.update(response); + } + if (response.getStatus() == ClientFeaturesResponse.Status.UNAVAILABLE) { if (unleashConfig.isSynchronousFetchOnInitialisation()) { throw new UnleashException( String.format( @@ -111,12 +114,15 @@ private Runnable runSteadyStateFetch(final Consumer handler) { if (throttler.performAction()) { try { ClientFeaturesResponse response = featureFetcher.fetchFeatures(); - eventEmitter.update(response); if (response.getStatus() == ClientFeaturesResponse.Status.CHANGED) { - updateFeatures(response); + String currentState = applyClientFeatures(response); + eventEmitter.update(ClientFeaturesResponse.updated(currentState)); } else if (response.getStatus() == ClientFeaturesResponse.Status.UNAVAILABLE) { + eventEmitter.update(response); throttler.handleHttpErrorCodes(response.getHttpStatusCode()); return; + } else { + eventEmitter.update(response); } throttler.decrementFailureCountAndResetSkips(); } catch (UnleashException e) { @@ -130,12 +136,14 @@ private Runnable runSteadyStateFetch(final Consumer handler) { }; } - private void updateFeatures(ClientFeaturesResponse response) + private String applyClientFeatures(ClientFeaturesResponse response) throws YggdrasilInvalidInputException { String clientFeatures = response.getClientFeatures().get(); this.engine.takeState(clientFeatures); - this.featureBackupHandler.write(clientFeatures); + String currentState = this.engine.getState(); + this.featureBackupHandler.write(currentState); eventEmitter.ready(); + return currentState; } public Integer getFailures() { diff --git a/src/main/java/io/getunleash/repository/StreamingFeatureFetcherImpl.java b/src/main/java/io/getunleash/repository/StreamingFeatureFetcherImpl.java index b841ee875..6023deb81 100644 --- a/src/main/java/io/getunleash/repository/StreamingFeatureFetcherImpl.java +++ b/src/main/java/io/getunleash/repository/StreamingFeatureFetcherImpl.java @@ -131,7 +131,7 @@ synchronized void handleStreamingUpdate(String data) throws YggdrasilInvalidInpu String currentState = engine.getState(); featureBackupHandler.write(currentState); - ClientFeaturesResponse response = ClientFeaturesResponse.updated(data); + ClientFeaturesResponse response = ClientFeaturesResponse.updated(currentState); eventDispatcher.update(response); if (!ready) { From 128e682aeab938e99abe057beb4e7f987f97f64e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Mon, 9 Feb 2026 10:28:57 +0100 Subject: [PATCH 2/4] docs: clarify v10 strategy API docs --- README.md | 16 ++++++---------- v10_MIGRATION_GUIDE.md | 6 ++++++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 58854c669..40508cce8 100644 --- a/README.md +++ b/README.md @@ -110,21 +110,17 @@ You can also **provide an [Unleash context](https://docs.getunleash.io/reference ### Activation strategies -The Java client comes with implementations for the built-in activation strategies provided by Unleash: +The Java client supports Unleash built-in activation strategies (such as `default`, `userWithId`, `gradualRolloutRandom`, `gradualRolloutUserId`, `gradualRolloutSessionId`, `remoteAddress`, and `applicationHostname`). -- DefaultStrategy -- UserWithIdStrategy -- GradualRolloutRandomStrategy -- GradualRolloutUserWithIdStrategy -- GradualRolloutSessionIdStrategy -- RemoteAddressStrategy -- ApplicationHostnameStrategy +As of v10, these built-ins are evaluated by the embedded Yggdrasil engine and are not exposed as public Java strategy classes in this SDK. Read more about the strategies in the [activation strategies reference documentation](https://docs.getunleash.io/reference/activation-strategies). #### Custom strategies -You may also specify and implement your own strategy. The specification must be registered in the Unleash UI and -you must register the strategy implementation when you set up Unleash. +You may also specify and implement your own strategy. The specification must be registered in the Unleash UI and you must register the strategy implementation when you set up Unleash. + +You can also provide a `fallbackStrategy` via `UnleashConfig` if the client receives a strategy +name it does not recognize. ```java Strategy s1 = new MyAwesomeStrategy(); diff --git a/v10_MIGRATION_GUIDE.md b/v10_MIGRATION_GUIDE.md index ddf880934..63ed467ef 100644 --- a/v10_MIGRATION_GUIDE.md +++ b/v10_MIGRATION_GUIDE.md @@ -16,6 +16,12 @@ The Bootstrapping interface now requires an `Optional` to be returned ra The strategy interface has changed to only include the two methods `getName` and `isEnabled`. `isEnabled` now requires both a parameter map and an `UnleashContext`. This only affects users who are implementing custom or fallback strategies. +Built-in strategy classes from `io.getunleash.strategy` (for example +`GradualRolloutRandomStrategy`, `GradualRolloutUserWithIdStrategy`, and +`GradualRolloutSessionIdStrategy`) are no longer part of the Java SDK public API in v10. + +These strategies are evaluated internally by Yggdrasil. There is no class-for-class replacement to import for built-ins; use the built-ins via Unleash configuration, and implement `Strategy` only for custom or fallback behavior. + ## Events The following subscriber functions are no longer available: `togglesBackedUp`, `toggleBackupRestored`, and `togglesBootstrapped`. Subscribing to `featuresBackedUp`, `featuresBackupRestored`, and `featuresBootstrapped` respectively serves the same purpose. These subscribers no longer yield events that contain the full feature flag definition, instead, they expose a `getFeatures` method which returns a list of lightweight Java objects containing the feature name, the type of flag, and the project it's bound to. From c89802c703eb39654c3e15560e28674bce2ee5bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Mon, 9 Feb 2026 10:42:08 +0100 Subject: [PATCH 3/4] Revert unnecessary changes --- .../repository/PollingFeatureFetcher.java | 22 ++++++------------- .../StreamingFeatureFetcherImpl.java | 2 +- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/main/java/io/getunleash/repository/PollingFeatureFetcher.java b/src/main/java/io/getunleash/repository/PollingFeatureFetcher.java index ccfee5f31..a5c5eba2d 100644 --- a/src/main/java/io/getunleash/repository/PollingFeatureFetcher.java +++ b/src/main/java/io/getunleash/repository/PollingFeatureFetcher.java @@ -85,13 +85,10 @@ private Runnable runInitialFetch(final Consumer handler) { return () -> { try { ClientFeaturesResponse response = featureFetcher.fetchFeatures(); + eventEmitter.update(response); if (response.getStatus() == ClientFeaturesResponse.Status.CHANGED) { - String currentState = applyClientFeatures(response); - eventEmitter.update(ClientFeaturesResponse.updated(currentState)); - } else { - eventEmitter.update(response); - } - if (response.getStatus() == ClientFeaturesResponse.Status.UNAVAILABLE) { + updateFeatures(response); + } else if (response.getStatus() == ClientFeaturesResponse.Status.UNAVAILABLE) { if (unleashConfig.isSynchronousFetchOnInitialisation()) { throw new UnleashException( String.format( @@ -114,15 +111,12 @@ private Runnable runSteadyStateFetch(final Consumer handler) { if (throttler.performAction()) { try { ClientFeaturesResponse response = featureFetcher.fetchFeatures(); + eventEmitter.update(response); if (response.getStatus() == ClientFeaturesResponse.Status.CHANGED) { - String currentState = applyClientFeatures(response); - eventEmitter.update(ClientFeaturesResponse.updated(currentState)); + updateFeatures(response); } else if (response.getStatus() == ClientFeaturesResponse.Status.UNAVAILABLE) { - eventEmitter.update(response); throttler.handleHttpErrorCodes(response.getHttpStatusCode()); return; - } else { - eventEmitter.update(response); } throttler.decrementFailureCountAndResetSkips(); } catch (UnleashException e) { @@ -136,14 +130,12 @@ private Runnable runSteadyStateFetch(final Consumer handler) { }; } - private String applyClientFeatures(ClientFeaturesResponse response) + private void updateFeatures(ClientFeaturesResponse response) throws YggdrasilInvalidInputException { String clientFeatures = response.getClientFeatures().get(); this.engine.takeState(clientFeatures); - String currentState = this.engine.getState(); - this.featureBackupHandler.write(currentState); + this.featureBackupHandler.write(clientFeatures); eventEmitter.ready(); - return currentState; } public Integer getFailures() { diff --git a/src/main/java/io/getunleash/repository/StreamingFeatureFetcherImpl.java b/src/main/java/io/getunleash/repository/StreamingFeatureFetcherImpl.java index 6023deb81..b841ee875 100644 --- a/src/main/java/io/getunleash/repository/StreamingFeatureFetcherImpl.java +++ b/src/main/java/io/getunleash/repository/StreamingFeatureFetcherImpl.java @@ -131,7 +131,7 @@ synchronized void handleStreamingUpdate(String data) throws YggdrasilInvalidInpu String currentState = engine.getState(); featureBackupHandler.write(currentState); - ClientFeaturesResponse response = ClientFeaturesResponse.updated(currentState); + ClientFeaturesResponse response = ClientFeaturesResponse.updated(data); eventDispatcher.update(response); if (!ready) { From 90492b166c52152cde2db2e1ad2add52ce4616f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gast=C3=B3n=20Fournier?= Date: Mon, 9 Feb 2026 10:46:16 +0100 Subject: [PATCH 4/4] docs: add v11/v12 migration guides and version guidance --- README.md | 8 ++++++-- v10_MIGRATION_GUIDE.md | 11 +++++++++++ v11_MIGRATION_GUIDE.md | 22 ++++++++++++++++++++++ v12_MIGRATION_GUIDE.md | 22 ++++++++++++++++++++++ 4 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 v11_MIGRATION_GUIDE.md create mode 100644 v12_MIGRATION_GUIDE.md diff --git a/README.md b/README.md index 40508cce8..3adab8462 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,13 @@ Unleash is a private, secure, and scalable [feature management platform](http You can use this client with [Unleash Enterprise](https://www.getunleash.io/pricing?utm_source=readme&utm_medium=java) or [Unleash Open Source](https://github.com/Unleash/unleash). -> **Migrating to v10** +> **Migration guides** > -> If you're using `MoreOperations`, custom or fallback strategies, subscribers or bootstrapping, please see the full [migration guide](v10_MIGRATION_GUIDE.md) for details. If you use GraalVM or Quarkus, please hold off on upgrading to v10, support is planned but not implemented. +> - [Migrating to v10](v10_MIGRATION_GUIDE.md) +> - [Migrating to v11](v11_MIGRATION_GUIDE.md) +> - [Migrating to v12](v12_MIGRATION_GUIDE.md) +> +> For ongoing updates, prefer v12. The latest patch releases of v10, v11, and v12 are currently aligned on the same optimized implementation path. ## Java Version Compatibility diff --git a/v10_MIGRATION_GUIDE.md b/v10_MIGRATION_GUIDE.md index 63ed467ef..69e896170 100644 --- a/v10_MIGRATION_GUIDE.md +++ b/v10_MIGRATION_GUIDE.md @@ -2,6 +2,17 @@ This guide highlights the key changes you need to be aware of when upgrading to v10.0.0 of the Unleash client. +## Version guidance + +For ongoing updates, prefer v12. + +The latest patch releases in v10, v11, and v12 are currently aligned on the same optimized +implementation path. + +Early v11 introduced a WASM-engine direction that we later reverted in v11 patch releases. + +If you need to stay on a specific major version, make sure you run the latest patch in that major. + ## Custom bootstrapping The Bootstrapping interface now requires an `Optional` to be returned rather than a `String`. If the bootstrapper fails to load the feature set, return an `Optional` of empty. diff --git a/v11_MIGRATION_GUIDE.md b/v11_MIGRATION_GUIDE.md new file mode 100644 index 000000000..7e6ded0d8 --- /dev/null +++ b/v11_MIGRATION_GUIDE.md @@ -0,0 +1,22 @@ +# Migrating to Unleash-Client-Java 11.x + +This guide highlights what to know when upgrading from v10 to v11. + +## Java runtime requirement + +v11 requires Java 11+. + +## Migration path + +For API-level migration details, refer to the v10 guide: +[v10_MIGRATION_GUIDE.md](v10_MIGRATION_GUIDE.md) + +The latest patch releases in v10, v11, and v12 are currently aligned on the same optimized +implementation path. + +Early v11 introduced a WASM-engine direction that we later reverted in v11 patch releases. + +## Recommendation + +v11 is maintained for stability and critical fixes when needed, but for ongoing updates and new +improvements, prefer v12. diff --git a/v12_MIGRATION_GUIDE.md b/v12_MIGRATION_GUIDE.md new file mode 100644 index 000000000..74e339db4 --- /dev/null +++ b/v12_MIGRATION_GUIDE.md @@ -0,0 +1,22 @@ +# Migrating to Unleash-Client-Java 12.x + +This guide highlights what to know when upgrading from v11 to v12. + +## Java runtime requirement + +v12 requires Java 11+. + +## Migration path + +For API-level migration details that still apply, refer to the v10 guide: +[v10_MIGRATION_GUIDE.md](v10_MIGRATION_GUIDE.md) + +For current guidance on the v11 line, refer to: +[v11_MIGRATION_GUIDE.md](v11_MIGRATION_GUIDE.md) + +The latest patch releases in v10, v11, and v12 are currently aligned on the same optimized +implementation path. + +## Recommendation + +Use v12 for ongoing updates and future improvements.