Skip to content
Merged
4 changes: 4 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
4.18.2 (Oct 15, 2025)
- Fixed an issue where Manager.splitNames() return incorrect formatted result using redis storage and no custom prefix.
- Added using String only parameter for treatments in FallbackTreatmentConfiguration class.

4.18.1 (Sep 30, 2025)
- Fixed an issue where Streaming client hangs during token renew process.

Expand Down
4 changes: 2 additions & 2 deletions client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
<parent>
<groupId>io.split.client</groupId>
<artifactId>java-client-parent</artifactId>
<version>4.18.1</version>
<version>4.18.2</version>
</parent>
<version>4.18.1</version>
<version>4.18.2</version>
<artifactId>java-client</artifactId>
<packaging>jar</packaging>
<name>Java Client</name>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.split.client.dtos;

import java.util.HashMap;
import java.util.Map;

public class FallbackTreatmentsConfiguration {
Expand All @@ -11,9 +12,54 @@ public FallbackTreatmentsConfiguration(FallbackTreatment globalFallbackTreatment
_byFlagFallbackTreatment = byFlagFallbackTreatment;
}

public FallbackTreatmentsConfiguration(Map<String, FallbackTreatment> byFlagFallbackTreatment) {
_globalFallbackTreatment = null;
_byFlagFallbackTreatment = byFlagFallbackTreatment;
}

public FallbackTreatmentsConfiguration(HashMap<String, String> byFlagFallbackTreatment) {
_globalFallbackTreatment = null;
_byFlagFallbackTreatment = buildByFlagFallbackTreatment(byFlagFallbackTreatment);
}

public FallbackTreatmentsConfiguration(FallbackTreatment globalFallbackTreatment) {
_globalFallbackTreatment = globalFallbackTreatment;
_byFlagFallbackTreatment = null;
}

public FallbackTreatmentsConfiguration(String globalFallbackTreatment, Map<String, FallbackTreatment> byFlagFallbackTreatment) {
_globalFallbackTreatment = new FallbackTreatment(globalFallbackTreatment);
_byFlagFallbackTreatment = byFlagFallbackTreatment;
}

public FallbackTreatmentsConfiguration(String globalFallbackTreatment) {
_globalFallbackTreatment = new FallbackTreatment(globalFallbackTreatment);
_byFlagFallbackTreatment = null;
}


public FallbackTreatmentsConfiguration(String globalFallbackTreatment, HashMap<String, String> byFlagFallbackTreatment) {
_globalFallbackTreatment = new FallbackTreatment(globalFallbackTreatment);
_byFlagFallbackTreatment = buildByFlagFallbackTreatment(byFlagFallbackTreatment);
}

public FallbackTreatmentsConfiguration(FallbackTreatment globalFallbackTreatment, HashMap<String, String> byFlagFallbackTreatment) {
_globalFallbackTreatment = globalFallbackTreatment;
_byFlagFallbackTreatment = buildByFlagFallbackTreatment(byFlagFallbackTreatment);
}

public FallbackTreatment getGlobalFallbackTreatment() {
return _globalFallbackTreatment;
}

public Map<String, FallbackTreatment> getByFlagFallbackTreatment() { return _byFlagFallbackTreatment;}

private Map<String, FallbackTreatment> buildByFlagFallbackTreatment(Map<String, String> byFlagFallbackTreatment) {
Map<String, FallbackTreatment> result = new HashMap<>();
for (Map.Entry<String, String> entry : byFlagFallbackTreatment.entrySet()) {
result.put(entry.getKey(), new FallbackTreatment(entry.getValue()));
}

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public FetchResult forceRefresh(FetchOptions options) {
Thread.currentThread().interrupt();
return new FetchResult(false, true, new HashSet<>());
} catch (Exception e) {
_log.error("RefreshableSplitFetcher failed: " + e.getMessage());
_log.error("SplitFetcherImp failed: " + e.getMessage());
if (_log.isDebugEnabled()) {
_log.debug("Reason:", e);
}
Expand Down Expand Up @@ -166,4 +166,4 @@ private Set<String> runWithoutExceptionHandling(FetchOptions options) throws Int

return segments;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.split.client;

import io.split.client.dtos.FallbackTreatment;
import io.split.client.dtos.FallbackTreatmentsConfiguration;
import org.junit.Assert;
import org.junit.Test;

Expand All @@ -12,10 +14,14 @@ public class JsonLocalhostSplitFactoryTest {

@Test
public void works() throws IOException, URISyntaxException, InterruptedException, TimeoutException {
FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-global"),
new HashMap<String, FallbackTreatment>() {{ put("feature", new FallbackTreatment("off-local", "{\"prop2\", \"val2\"}")); }});

SplitClientConfig config = SplitClientConfig.builder()
.splitFile("src/test/resources/splits_localhost.json")
.segmentDirectory("src/test/resources")
.setBlockUntilReadyTimeout(10000)
.fallbackTreatments(fallbackTreatmentsConfiguration)
.build();
SplitFactory splitFactory = SplitFactoryBuilder.build("localhost", config);
SplitClient client = splitFactory.client();
Expand All @@ -30,6 +36,9 @@ public void works() throws IOException, URISyntaxException, InterruptedException
Assert.assertEquals("off", client.getTreatment("bilal", "test_split"));
Assert.assertEquals("on", client.getTreatment("bilal", "push_test"));
Assert.assertEquals("on_whitelist", client.getTreatment("admin", "push_test"));
Assert.assertEquals("off-local", client.getTreatment("bilal", "feature"));
Assert.assertEquals("on-global", client.getTreatment("bilal", "feature2"));

client.destroy();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package io.split.client;

import com.google.common.collect.Maps;
import io.split.client.dtos.FallbackTreatment;
import io.split.client.dtos.FallbackTreatmentsConfiguration;
import io.split.client.utils.LocalhostUtils;
import io.split.grammar.Treatments;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.assertEquals;
Expand All @@ -35,7 +39,9 @@ public void works() throws IOException, URISyntaxException, InterruptedException

LocalhostUtils.writeFile(file, map);

SplitClientConfig config = SplitClientConfig.builder().splitFile(folder.getRoot().getAbsolutePath()).build();
SplitClientConfig config = SplitClientConfig.builder()
.splitFile(folder.getRoot().getAbsolutePath())
.build();
SplitFactory splitFactory = SplitFactoryBuilder.build("localhost", config);
SplitClient client = splitFactory.client();

Expand All @@ -48,4 +54,30 @@ public void works() throws IOException, URISyntaxException, InterruptedException
assertEquals("a", client.getTreatment("user1", "test"));
assertEquals("a", client.getTreatment("user2", "test"));
}

@Test
public void testFallbackTreatments() throws IOException, URISyntaxException, InterruptedException {
File file = folder.newFile(LegacyLocalhostSplitChangeFetcher.FILENAME);

Map<SplitAndKey, LocalhostSplit> map = Maps.newHashMap();
map.put(SplitAndKey.of("onboarding"), LocalhostSplit.of("on"));
map.put(SplitAndKey.of("onboarding", "user1"), LocalhostSplit.of("off"));
map.put(SplitAndKey.of("onboarding", "user2"), LocalhostSplit.of("off"));
map.put(SplitAndKey.of("test"), LocalhostSplit.of("a"));

LocalhostUtils.writeFile(file, map);

FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-global"),
new HashMap<String, FallbackTreatment>() {{ put("feature", new FallbackTreatment("off-local", "{\"prop2\", \"val2\"}")); }});

SplitClientConfig config = SplitClientConfig.builder()
.splitFile(folder.getRoot().getAbsolutePath())
.fallbackTreatments(fallbackTreatmentsConfiguration)
.build();
SplitFactory splitFactory = SplitFactoryBuilder.build("localhost", config);
SplitClient client = splitFactory.client();

assertEquals("off-local", client.getTreatment("user1", "feature"));
assertEquals("on-global", client.getTreatment("user1", "feature2"));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.split.client;

import io.split.client.dtos.FallbackTreatment;
import io.split.client.dtos.FallbackTreatmentsConfiguration;
import io.split.client.utils.LocalhostUtils;
import io.split.grammar.Treatments;
import org.junit.Rule;
Expand All @@ -11,10 +13,7 @@
import java.io.IOException;
import java.io.StringWriter;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
Expand Down Expand Up @@ -106,4 +105,59 @@ public void works() throws IOException, URISyntaxException {
// unchanged
assertThat(client.getTreatment("user_b", "split_1"), is(equalTo("on")));
}

@Test
public void testFallbackTreatment() throws IOException, URISyntaxException {
File file = folder.newFile(SplitClientConfig.LOCALHOST_DEFAULT_FILE);

List<Map<String, Object>> allSplits = new ArrayList();

Map<String, Object> split1_user_a = new LinkedHashMap<>();
Map<String, Object> split1_user_a_data = new LinkedHashMap<>();
split1_user_a_data.put("keys", "user_a");
split1_user_a_data.put("treatment", "off");
split1_user_a_data.put("config", "{ \"size\" : 20 }");
split1_user_a.put("split_1", split1_user_a_data);
allSplits.add(split1_user_a);

Map<String, Object> split1_user_b = new LinkedHashMap<>();
Map<String, Object> split1_user_b_data = new LinkedHashMap<>();
split1_user_b_data.put("keys", "user_b");
split1_user_b_data.put("treatment", "on");
split1_user_b.put("split_1", split1_user_b_data);
allSplits.add(split1_user_b);

Map<String, Object> split2_user_a = new LinkedHashMap<>();
Map<String, Object> split2_user_a_data = new LinkedHashMap<>();
split2_user_a_data.put("keys", "user_a");
split2_user_a_data.put("treatment", "off");
split2_user_a_data.put("config", "{ \"size\" : 20 }");
split2_user_a.put("split_2", split2_user_a_data);
allSplits.add(split2_user_a);


Yaml yaml = new Yaml();
StringWriter writer = new StringWriter();
yaml.dump(allSplits, writer);

String expectedYaml = "- split_1: {keys: user_a, treatment: 'off', config: '{ \"size\" : 20 }'}\n" +
"- split_1: {keys: user_b, treatment: 'on'}\n" +
"- split_2: {keys: user_a, treatment: 'off', config: '{ \"size\" : 20 }'}\n";

assertEquals(expectedYaml, writer.toString());

LocalhostUtils.writeFile(file, writer);
FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-global"),
new HashMap<String, FallbackTreatment>() {{ put("feature", new FallbackTreatment("off-local", "{\"prop2\", \"val2\"}")); }});

SplitClientConfig config = SplitClientConfig.builder()
.splitFile(file.getAbsolutePath())
.fallbackTreatments(fallbackTreatmentsConfiguration)
.build();
SplitFactory splitFactory = SplitFactoryBuilder.build("localhost", config);
SplitClient client = splitFactory.client();

assertEquals("off-local", client.getTreatment("user1", "feature"));
assertEquals("on-global", client.getTreatment("user1", "feature2"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -367,18 +367,18 @@ public void mustUseP12PassKeyWithProxyMtls() throws MalformedURLException, FileN
@Test
public void fallbackTreatmentCheckRegex() {
SplitClientConfig config = SplitClientConfig.builder()
.fallbackTreatments(new FallbackTreatmentsConfiguration(new FallbackTreatment("12#2"), null))
.fallbackTreatments(new FallbackTreatmentsConfiguration(new FallbackTreatment("12#2")))
.build();
Assert.assertEquals(null, config.fallbackTreatments().getGlobalFallbackTreatment().getTreatment());

config = SplitClientConfig.builder()
.fallbackTreatments(new FallbackTreatmentsConfiguration(null, new HashMap<String, FallbackTreatment>() {{ put("flag", new FallbackTreatment("12#2")); }} ))
.fallbackTreatments(new FallbackTreatmentsConfiguration(new HashMap<String, FallbackTreatment>() {{ put("flag", new FallbackTreatment("12#2")); }} ))
.build();
Assert.assertEquals(0, config.fallbackTreatments().getByFlagFallbackTreatment().size());

config = SplitClientConfig.builder()
.fallbackTreatments(new FallbackTreatmentsConfiguration(
new FallbackTreatment("on"),
"on",
new HashMap<String, FallbackTreatment>() {{ put("flag", new FallbackTreatment("off")); }} ))
.build();
Assert.assertEquals("on", config.fallbackTreatments().getGlobalFallbackTreatment().getTreatment());
Expand Down
10 changes: 4 additions & 6 deletions client/src/test/java/io/split/client/SplitClientImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2308,8 +2308,7 @@ public void fallbackTreatmentWithExceptionsResult() {

String fallbcakConfigGlobal = "{\"prop1\", \"val1\"}";
FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(
new FallbackTreatment("on", fallbcakConfigGlobal),
null);
new FallbackTreatment("on", fallbcakConfigGlobal));
FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration);

SplitClientImpl client = new SplitClientImpl(
Expand Down Expand Up @@ -2388,7 +2387,7 @@ public void fallbackTreatmentWithExceptionsResult() {
assertEquals("off", client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("feature").treatment());
assertEquals(fallbcakConfigByFlag, client.getTreatmentsWithConfigByFlagSets("adil@relateiq.com", Arrays.asList("flag")).get("feature").config());

fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(null,
fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(
new HashMap<String, FallbackTreatment>() {{ put("feature", new FallbackTreatment("off", fallbcakConfigByFlag)); }});

fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration);
Expand Down Expand Up @@ -2458,8 +2457,7 @@ public void fallbackTreatmentWithSplitNotFoundResult() {

String fallbcakConfigGlobal = "{\"prop1\", \"val1\"}";
FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(
new FallbackTreatment("on", fallbcakConfigGlobal),
null);
new FallbackTreatment("on", fallbcakConfigGlobal));
FallbackTreatmentCalculator fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration);

SplitClientImpl client = new SplitClientImpl(
Expand Down Expand Up @@ -2573,7 +2571,7 @@ public void fallbackTreatmentWithSplitNotFoundResult() {
assertEquals("on", results.get("test3").treatment());
assertEquals(fallbcakConfigGlobal, results.get("test3").config());

fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(null,
fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(
new HashMap<String, FallbackTreatment>() {{ put("test2", new FallbackTreatment("off-fallback", fallbcakConfigByFlag)); }});

fallbackTreatmentCalculator = new FallbackTreatmentCalculatorImp(fallbackTreatmentsConfiguration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1282,8 +1282,7 @@ public MockResponse dispatch(RecordedRequest request) {

server.start();
String serverURL = String.format("http://%s:%s", server.getHostName(), server.getPort());
FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-fallback", "{\"prop1\", \"val1\"}"),
null);
FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-fallback", "{\"prop1\", \"val1\"}"));

SplitClientConfig config = SplitClientConfig.builder()
.setBlockUntilReadyTimeout(10000)
Expand Down Expand Up @@ -1354,7 +1353,7 @@ public MockResponse dispatch(RecordedRequest request) {

server.start();
String serverURL = String.format("http://%s:%s", server.getHostName(), server.getPort());
FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(null,
FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(
new HashMap<String, FallbackTreatment>() {{ put("feature", new FallbackTreatment("off-fallback", "{\"prop2\", \"val2\"}")); }});

SplitClientConfig config = SplitClientConfig.builder()
Expand Down Expand Up @@ -1427,8 +1426,7 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio

server.start();
String serverURL = String.format("http://%s:%s", server.getHostName(), server.getPort());
FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration(new FallbackTreatment("on-fallback", "{\"prop1\", \"val1\"}"),
null);
FallbackTreatmentsConfiguration fallbackTreatmentsConfiguration = new FallbackTreatmentsConfiguration("on-fallback");

SplitClientConfig config = SplitClientConfig.builder()
.setBlockUntilReadyTimeout(10000)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public void testFactoryInstantiation() throws Exception {
.authServiceURL(AUTH_SERVICE)
.setBlockUntilReadyTimeout(10000)
.telemetryURL(SplitClientConfig.TELEMETRY_ENDPOINT)
.fallbackTreatments(new FallbackTreatmentsConfiguration(new FallbackTreatment("on"), null))
.fallbackTreatments(new FallbackTreatmentsConfiguration(new FallbackTreatment("on")))
.build();
SplitFactoryImpl splitFactory = new SplitFactoryImpl(API_KEY, splitClientConfig);

Expand Down
Loading