Skip to content

Commit 6d0ccc1

Browse files
authored
Merge branch 'master' into copilot/check-for-pr-errors
2 parents 97d6538 + 0c9c329 commit 6d0ccc1

File tree

22 files changed

+392
-106
lines changed

22 files changed

+392
-106
lines changed

.github/workflows/java.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,18 @@ jobs:
4949
strategy:
5050
fail-fast: false
5151
matrix:
52-
jdk: [8, 9]
52+
# github not support JVM 8 anymore
53+
jdk: [11, 17]
5354
optimizely_default_parser: [GSON_CONFIG_PARSER, JACKSON_CONFIG_PARSER, JSON_CONFIG_PARSER, JSON_SIMPLE_CONFIG_PARSER]
5455
steps:
5556
- name: checkout
5657
uses: actions/checkout@v4
5758

5859
- name: set up JDK ${{ matrix.jdk }}
59-
uses: AdoptOpenJDK/install-jdk@v1
60+
uses: actions/setup-java@v4
6061
with:
61-
version: ${{ matrix.jdk }}
62-
architecture: x64
62+
java-version: ${{ matrix.jdk }}
63+
distribution: 'temurin'
6364

6465
- name: Grant execute permission for gradlew
6566
run: chmod +x gradlew

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
# Optimizely Java X SDK Changelog
22

3+
## [4.3.0]
4+
Dec 10th, 2025
5+
6+
### New Features
7+
- **CMAB (Contextual Multi-Armed Bandit) Support**: Added support for CMAB experiments with new configuration options and cache control ([#577](https://github.com/optimizely/java-sdk/pull/577), [#578](https://github.com/optimizely/java-sdk/pull/578), [#579](https://github.com/optimizely/java-sdk/pull/579), [#582](https://github.com/optimizely/java-sdk/pull/582), [#583](https://github.com/optimizely/java-sdk/pull/583), [#584](https://github.com/optimizely/java-sdk/pull/584), [#585](https://github.com/optimizely/java-sdk/pull/585), [#590](https://github.com/optimizely/java-sdk/pull/590), [#593](https://github.com/optimizely/java-sdk/pull/593))
8+
- **Add Holdouts Feature**: Add Holdout support for feature experimentation ([#572](https://github.com/optimizely/java-sdk/pull/572), [#576](https://github.com/optimizely/java-sdk/pull/576))
9+
- **Multi-Region Support for Data Hosting**: Added SDK support for multi-region data hosting ([#573](https://github.com/optimizely/java-sdk/pull/573))
10+
11+
### API Changes
12+
- **OptimizelyUserContext**: New asynchronous decision-making methods
13+
- `decideAsync()`: Asynchronous method to make a decision for a single flag with CMAB support
14+
- `decideAllAsync()`: Asynchronous method to make decisions for all flags
15+
- `decideForKeysAsync()`: Asynchronous method to make decisions for multiple flag keys
16+
17+
- **Client Initialization**:
18+
- `CmabClientConfig` can be injected when initializing the client for custom CMAB configuration
19+
- `CmabService` can be provided to `OptimizelyFactory` for custom CMAB service implementation
20+
21+
- **New Decide Options**: Added cache control options for CMAB
22+
- `IGNORE_CMAB_CACHE`: Skip reading from CMAB cache
23+
- `RESET_CMAB_CACHE`: Clear and reset CMAB cache before decision
24+
- `INVALIDATE_USER_CMAB_CACHE`: Invalidate cache entries for specific user
25+
326
## [4.2.2]
427
May 28th, 2025
528

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ dependencies {
4444
compile 'com.optimizely.ab:core-api:{VERSION}'
4545
compile 'com.optimizely.ab:core-httpclient-impl:{VERSION}'
4646
// The SDK integrates with multiple JSON parsers, here we use Jackson.
47-
compile 'com.fasterxml.jackson.core:jackson-core:2.7.1'
48-
compile 'com.fasterxml.jackson.core:jackson-annotations:2.7.1'
49-
compile 'com.fasterxml.jackson.core:jackson-databind:2.7.1'
47+
compile 'com.fasterxml.jackson.core:jackson-core:2.13.5'
48+
compile 'com.fasterxml.jackson.core:jackson-annotations:2.13.5'
49+
compile 'com.fasterxml.jackson.core:jackson-databind:2.13.5'
5050
}
5151
```
5252

core-api/src/main/java/com/optimizely/ab/Optimizely.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ private void sendImpression(@Nonnull ProjectConfig projectConfig,
318318
* @param variation the variation that was returned from activate.
319319
* @param flagKey It can either be empty if ruleType is experiment or it's feature key in case ruleType is feature-test or rollout
320320
* @param ruleType It can either be experiment in case impression event is sent from activate or it's feature-test or rollout
321-
* @param cmabUUID The cmabUUID if the experiment is a cmab experiment.
321+
* @param cmabUuid The cmabUuid if the experiment is a cmab experiment.
322322
*/
323323
private boolean sendImpression(@Nonnull ProjectConfig projectConfig,
324324
@Nullable ExperimentCore experiment,
@@ -328,7 +328,7 @@ private boolean sendImpression(@Nonnull ProjectConfig projectConfig,
328328
@Nonnull String flagKey,
329329
@Nonnull String ruleType,
330330
@Nonnull boolean enabled,
331-
@Nullable String cmabUUID) {
331+
@Nullable String cmabUuid) {
332332

333333
UserEvent userEvent = UserEventFactory.createImpressionEvent(
334334
projectConfig,
@@ -339,11 +339,12 @@ private boolean sendImpression(@Nonnull ProjectConfig projectConfig,
339339
flagKey,
340340
ruleType,
341341
enabled,
342-
cmabUUID);
342+
cmabUuid);
343343

344344
if (userEvent == null) {
345345
return false;
346346
}
347+
eventProcessor.getClass().getName();
347348
eventProcessor.process(userEvent);
348349
if (experiment != null) {
349350
logger.info("Activating user \"{}\" in experiment \"{}\".", userId, experiment.getKey());
@@ -501,7 +502,7 @@ private Boolean isFeatureEnabled(@Nonnull ProjectConfig projectConfig,
501502
if (featureDecision.decisionSource != null) {
502503
decisionSource = featureDecision.decisionSource;
503504
}
504-
String cmabUUID = featureDecision.cmabUUID;
505+
String cmabUuid = featureDecision.cmabUuid;
505506
if (featureDecision.variation != null) {
506507
// This information is only necessary for feature tests.
507508
// For rollouts experiments and variations are an implementation detail only.
@@ -524,7 +525,7 @@ private Boolean isFeatureEnabled(@Nonnull ProjectConfig projectConfig,
524525
featureKey,
525526
decisionSource.toString(),
526527
featureEnabled,
527-
cmabUUID);
528+
cmabUuid);
528529

529530
DecisionNotification decisionNotification = DecisionNotification.newFeatureDecisionNotificationBuilder()
530531
.withUserId(userId)
@@ -1339,7 +1340,7 @@ private OptimizelyDecision createOptimizelyDecision(
13391340
Map<String, Object> attributes = user.getAttributes();
13401341
Map<String, ?> copiedAttributes = new HashMap<>(attributes);
13411342

1342-
String cmabUUID = flagDecision.cmabUUID;
1343+
String cmabUuid = flagDecision.cmabUuid;
13431344

13441345
if (!allOptions.contains(OptimizelyDecideOption.DISABLE_DECISION_EVENT)) {
13451346
decisionEventDispatched = sendImpression(
@@ -1351,7 +1352,7 @@ private OptimizelyDecision createOptimizelyDecision(
13511352
flagKey,
13521353
decisionSource.toString(),
13531354
flagEnabled,
1354-
cmabUUID);
1355+
cmabUuid);
13551356
}
13561357

13571358
DecisionNotification decisionNotification = DecisionNotification.newFlagDecisionNotificationBuilder()

core-api/src/main/java/com/optimizely/ab/bucketing/DecisionService.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public DecisionResponse<Variation> getVariation(@Nonnull Experiment experiment,
169169
reasons.merge(decisionMeetAudience.getReasons());
170170
if (decisionMeetAudience.getResult()) {
171171
String bucketingId = getBucketingId(user.getUserId(), user.getAttributes());
172-
String cmabUUID = null;
172+
String cmabUuid = null;
173173
decisionVariation = bucketer.bucket(experiment, bucketingId, projectConfig, decisionPath);
174174
if (decisionPath == DecisionPath.WITH_CMAB && isCmabExperiment(experiment) && decisionVariation.getResult() != null) {
175175
// group-allocation and traffic-allocation checking passed for cmab
@@ -185,7 +185,7 @@ public DecisionResponse<Variation> getVariation(@Nonnull Experiment experiment,
185185
CmabDecision cmabResult = cmabDecision.getResult();
186186
if (cmabResult != null) {
187187
String variationId = cmabResult.getVariationId();
188-
cmabUUID = cmabResult.getCmabUUID();
188+
cmabUuid = cmabResult.getCmabUuid();
189189
variation = experiment.getVariationIdToVariationMap().get(variationId);
190190
}
191191
} else {
@@ -202,7 +202,7 @@ public DecisionResponse<Variation> getVariation(@Nonnull Experiment experiment,
202202
}
203203
}
204204

205-
return new DecisionResponse<>(variation, reasons, false, cmabUUID);
205+
return new DecisionResponse<>(variation, reasons, false, cmabUuid);
206206
}
207207

208208
String message = reasons.addInfo("User \"%s\" does not meet conditions to be in experiment \"%s\".", user.getUserId(), experiment.getKey());
@@ -337,7 +337,7 @@ public List<DecisionResponse<FeatureDecision>> getVariationsForFeatureList(@Non
337337
boolean error = decisionVariationResponse.isError();
338338

339339
if (decision != null) {
340-
decisions.add(new DecisionResponse(decision, reasons, error, decision.cmabUUID));
340+
decisions.add(new DecisionResponse(decision, reasons, error, decision.cmabUuid));
341341
continue;
342342
}
343343

@@ -396,21 +396,21 @@ DecisionResponse<FeatureDecision> getVariationFromExperiment(@Nonnull ProjectCon
396396
getVariationFromExperimentRule(projectConfig, featureFlag.getKey(), experiment, user, options, userProfileTracker, decisionPath);
397397
reasons.merge(decisionVariation.getReasons());
398398
Variation variation = decisionVariation.getResult();
399-
String cmabUUID = decisionVariation.getCmabUUID();
399+
String cmabUuid = decisionVariation.getCmabUuid();
400400
boolean error = decisionVariation.isError();
401401
if (error) {
402402
return new DecisionResponse(
403-
new FeatureDecision(experiment, variation, FeatureDecision.DecisionSource.FEATURE_TEST, cmabUUID),
403+
new FeatureDecision(experiment, variation, FeatureDecision.DecisionSource.FEATURE_TEST, cmabUuid),
404404
reasons,
405405
decisionVariation.isError(),
406-
cmabUUID);
406+
cmabUuid);
407407
}
408408
if (variation != null) {
409409
return new DecisionResponse(
410-
new FeatureDecision(experiment, variation, FeatureDecision.DecisionSource.FEATURE_TEST, cmabUUID),
410+
new FeatureDecision(experiment, variation, FeatureDecision.DecisionSource.FEATURE_TEST, cmabUuid),
411411
reasons,
412412
decisionVariation.isError(),
413-
cmabUUID);
413+
cmabUuid);
414414
}
415415
}
416416
} else {
@@ -845,7 +845,7 @@ private DecisionResponse<Variation> getVariationFromExperimentRule(@Nonnull Proj
845845

846846
variation = decisionResponse.getResult();
847847

848-
return new DecisionResponse<>(variation, reasons, decisionResponse.isError(), decisionResponse.getCmabUUID());
848+
return new DecisionResponse<>(variation, reasons, decisionResponse.isError(), decisionResponse.getCmabUuid());
849849
}
850850

851851
/**

core-api/src/main/java/com/optimizely/ab/bucketing/FeatureDecision.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class FeatureDecision {
4343
* The CMAB UUID for Contextual Multi-Armed Bandit experiments.
4444
*/
4545
@Nullable
46-
public String cmabUUID;
46+
public String cmabUuid;
4747

4848
public enum DecisionSource {
4949
FEATURE_TEST("feature-test"),
@@ -74,7 +74,7 @@ public FeatureDecision(@Nullable ExperimentCore experiment, @Nullable Variation
7474
this.experiment = experiment;
7575
this.variation = variation;
7676
this.decisionSource = decisionSource;
77-
this.cmabUUID = null;
77+
this.cmabUuid = null;
7878
}
7979

8080
/**
@@ -83,14 +83,14 @@ public FeatureDecision(@Nullable ExperimentCore experiment, @Nullable Variation
8383
* @param experiment The {@link ExperimentCore} the Feature is associated with.
8484
* @param variation The {@link Variation} the user was bucketed into.
8585
* @param decisionSource The source of the variation.
86-
* @param cmabUUID The CMAB UUID for Contextual Multi-Armed Bandit experiments.
86+
* @param cmabUuid The CMAB UUID for Contextual Multi-Armed Bandit experiments.
8787
*/
8888
public FeatureDecision(@Nullable ExperimentCore experiment, @Nullable Variation variation,
89-
@Nullable DecisionSource decisionSource, @Nullable String cmabUUID) {
89+
@Nullable DecisionSource decisionSource, @Nullable String cmabUuid) {
9090
this.experiment = experiment;
9191
this.variation = variation;
9292
this.decisionSource = decisionSource;
93-
this.cmabUUID = cmabUUID;
93+
this.cmabUuid = cmabUuid;
9494
}
9595

9696
@Override
@@ -103,14 +103,14 @@ public boolean equals(Object o) {
103103
if (variation != null ? !variation.equals(that.variation) : that.variation != null)
104104
return false;
105105
if (decisionSource != that.decisionSource) return false;
106-
return cmabUUID != null ? cmabUUID.equals(that.cmabUUID) : that.cmabUUID == null;
106+
return cmabUuid != null ? cmabUuid.equals(that.cmabUuid) : that.cmabUuid == null;
107107
}
108108

109109
@Override
110110
public int hashCode() {
111111
int result = variation != null ? variation.hashCode() : 0;
112112
result = 31 * result + (decisionSource != null ? decisionSource.hashCode() : 0);
113-
result = 31 * result + (cmabUUID != null ? cmabUUID.hashCode() : 0);
113+
result = 31 * result + (cmabUuid != null ? cmabUuid.hashCode() : 0);
114114
return result;
115115
}
116116
}

core-api/src/main/java/com/optimizely/ab/cmab/client/CmabClient.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ public interface CmabClient {
2424
* @param ruleId The rule/experiment ID
2525
* @param userId The user ID
2626
* @param attributes User attributes
27-
* @param cmabUUID The CMAB UUID
27+
* @param cmabUuid The CMAB UUID
2828
* @return CompletableFuture containing the variation ID as a String
2929
*/
30-
String fetchDecision(String ruleId, String userId, Map<String, Object> attributes, String cmabUUID);
30+
String fetchDecision(String ruleId, String userId, Map<String, Object> attributes, String cmabUuid);
3131
}

core-api/src/main/java/com/optimizely/ab/cmab/service/CmabCacheValue.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@
2020
public class CmabCacheValue {
2121
private final String attributesHash;
2222
private final String variationId;
23-
private final String cmabUUID;
23+
private final String cmabUuid;
2424

25-
public CmabCacheValue(String attributesHash, String variationId, String cmabUUID) {
25+
public CmabCacheValue(String attributesHash, String variationId, String cmabUuid) {
2626
this.attributesHash = attributesHash;
2727
this.variationId = variationId;
28-
this.cmabUUID = cmabUUID;
28+
this.cmabUuid = cmabUuid;
2929
}
3030

3131
public String getAttributesHash() {
@@ -37,15 +37,15 @@ public String getVariationId() {
3737
}
3838

3939
public String getCmabUuid() {
40-
return cmabUUID;
40+
return cmabUuid;
4141
}
4242

4343
@Override
4444
public String toString() {
4545
return "CmabCacheValue{" +
4646
"attributesHash='" + attributesHash + '\'' +
4747
", variationId='" + variationId + '\'' +
48-
", cmabUuid='" + cmabUUID + '\'' +
48+
", cmabUuid='" + cmabUuid + '\'' +
4949
'}';
5050
}
5151

@@ -56,11 +56,11 @@ public boolean equals(Object o) {
5656
CmabCacheValue that = (CmabCacheValue) o;
5757
return Objects.equals(attributesHash, that.attributesHash) &&
5858
Objects.equals(variationId, that.variationId) &&
59-
Objects.equals(cmabUUID, that.cmabUUID);
59+
Objects.equals(cmabUuid, that.cmabUuid);
6060
}
6161

6262
@Override
6363
public int hashCode() {
64-
return Objects.hash(attributesHash, variationId, cmabUUID);
64+
return Objects.hash(attributesHash, variationId, cmabUuid);
6565
}
6666
}

core-api/src/main/java/com/optimizely/ab/cmab/service/CmabDecision.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,26 @@
1919

2020
public class CmabDecision {
2121
private final String variationId;
22-
private final String cmabUUID;
22+
private final String cmabUuid;
2323

24-
public CmabDecision(String variationId, String cmabUUID) {
24+
public CmabDecision(String variationId, String cmabUuid) {
2525
this.variationId = variationId;
26-
this.cmabUUID = cmabUUID;
26+
this.cmabUuid = cmabUuid;
2727
}
2828

2929
public String getVariationId() {
3030
return variationId;
3131
}
3232

33-
public String getCmabUUID() {
34-
return cmabUUID;
33+
public String getCmabUuid() {
34+
return cmabUuid;
3535
}
3636

3737
@Override
3838
public String toString() {
3939
return "CmabDecision{" +
4040
"variationId='" + variationId + '\'' +
41-
", cmabUUID='" + cmabUUID + '\'' +
41+
", cmabUuid='" + cmabUuid + '\'' +
4242
'}';
4343
}
4444

@@ -48,11 +48,11 @@ public boolean equals(Object o) {
4848
if (o == null || getClass() != o.getClass()) return false;
4949
CmabDecision that = (CmabDecision) o;
5050
return Objects.equals(variationId, that.variationId) &&
51-
Objects.equals(cmabUUID, that.cmabUUID);
51+
Objects.equals(cmabUuid, that.cmabUuid);
5252
}
5353

5454
@Override
5555
public int hashCode() {
56-
return Objects.hash(variationId, cmabUUID);
56+
return Objects.hash(variationId, cmabUuid);
5757
}
5858
}

core-api/src/main/java/com/optimizely/ab/cmab/service/DefaultCmabService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import java.util.TreeMap;
2323
import java.util.concurrent.locks.ReentrantLock;
2424

25-
import com.optimizely.ab.event.internal.ClientEngineInfo;
2625
import org.slf4j.Logger;
2726
import org.slf4j.LoggerFactory;
2827

@@ -32,6 +31,7 @@
3231
import com.optimizely.ab.config.Attribute;
3332
import com.optimizely.ab.config.Experiment;
3433
import com.optimizely.ab.config.ProjectConfig;
34+
import com.optimizely.ab.event.internal.ClientEngineInfo;
3535
import com.optimizely.ab.internal.Cache;
3636
import com.optimizely.ab.internal.DefaultLRUCache;
3737
import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption;
@@ -107,7 +107,7 @@ public CmabDecision getDecision(ProjectConfig projectConfig, OptimizelyUserConte
107107
CmabDecision cmabDecision = fetchDecision(ruleId, userId, filteredAttributes);
108108
logger.debug("CMAB decision is {}", cmabDecision);
109109

110-
cmabCache.save(cacheKey, new CmabCacheValue(attributesHash, cmabDecision.getVariationId(), cmabDecision.getCmabUUID()));
110+
cmabCache.save(cacheKey, new CmabCacheValue(attributesHash, cmabDecision.getVariationId(), cmabDecision.getCmabUuid()));
111111

112112
return cmabDecision;
113113
} finally {

0 commit comments

Comments
 (0)