A Java SDK for Switcher API
- Overview
- Key Features
- Installation
- Quick Start
- Configuration
- Usage Patterns
- Operating Modes
- Advanced Features
- Testing
- Native Image Support
The Switcher Client SDK is a comprehensive Java library for integrating with Switcher API, a cloud-based feature flag management system. This SDK enables you to control feature toggles, A/B testing, and configuration management in your Java applications.
- π§ Flexible Configuration: Multiple configuration approaches to fit different application architectures
- π Local & Remote Operation: Works with remote API or local snapshot files for offline capability
- π Real-time Updates: Hot-swapping support with automatic snapshot synchronization
- π§ͺ Testing Support: Built-in testing utilities and annotations for automated testing
- β‘ Performance Optimized: Includes throttling, caching, and async execution capabilities
- π‘οΈ Resilient: Silent mode with automatic fallback mechanisms for high availability
- π Easy Debugging: Comprehensive execution history and metadata tracking
Add the following dependency to your pom.xml
:
<dependency>
<groupId>com.switcherapi</groupId>
<artifactId>switcher-client</artifactId>
<version>${switcher-client.version}</version>
</dependency>
mvn clean install
SDK Version | Java Version | Jakarta EE | Description |
---|---|---|---|
v1.x | Java 8+ | No | For traditional Java EE applications |
v2.x | Java 17+ | Yes | For Jakarta EE 9+ applications |
Here's a minimal example to get you started:
// 1. Define your feature flags
public class MyAppFeatures extends SwitcherContext {
@SwitcherKey
public static final String FEATURE_NEW_UI = "FEATURE_NEW_UI";
@SwitcherKey
public static final String FEATURE_PREMIUM = "FEATURE_PREMIUM";
}
// 2. Use in your application
public class MyService {
public void processRequest() {
if (MyAppFeatures.getSwitcher(FEATURE_NEW_UI).isItOn()) {
// Use new UI logic
} else {
// Use legacy UI logic
}
}
}
This approach automatically loads configuration from a properties file, ideal for applications with externalized configuration.
Create src/main/resources/switcherapi.properties
:
# Required Configuration
switcher.context=com.example.MyAppFeatures
switcher.url=https://api.switcherapi.com
switcher.apikey=YOUR_API_KEY
switcher.component=my-application
switcher.domain=MY_DOMAIN
# Optional Configuration
switcher.environment=default
switcher.timeout=3000
switcher.poolsize=2
Property | Required | Default | Description |
---|---|---|---|
switcher.context |
β | - | Fully qualified class name extending SwitcherContext |
switcher.url |
β | - | Switcher API endpoint URL |
switcher.apikey |
β | - | API key for authentication |
switcher.component |
β | - | Your application/component identifier |
switcher.domain |
β | - | Domain name in Switcher API |
switcher.environment |
β | default | Environment name (dev, staging, default) |
switcher.local |
β | false | Enable local-only mode |
switcher.check |
β | false | Validate switcher keys on startup |
switcher.relay.restrict |
β | true | Defines if client will trigger local snapshot relay verification |
switcher.snapshot.location |
β | - | Directory for snapshot files |
switcher.snapshot.auto |
β | false | Auto-load snapshots on startup |
switcher.snapshot.skipvalidation |
β | false | Skip snapshot validation on load |
switcher.snapshot.updateinterval |
β | - | Interval for automatic snapshot updates (e.g., "5s", "2m") |
switcher.snapshot.watcher |
β | false | Monitor snapshot files for changes |
switcher.silent |
β | - | Enable silent mode (e.g., "5s", "2m") |
switcher.timeout |
β | 3000 | API timeout in milliseconds |
switcher.poolsize |
β | 2 | Thread pool size for API calls |
switcher.regextimeout (v1-only) |
β | 3000 | Time in ms given to Timed Match Worker used for local Regex (ReDoS safety mechanism) |
switcher.truststore.path |
β | - | Path to custom truststore file |
switcher.truststore.password |
β | - | Password for custom truststore |
π‘ Environment Variables: Use
${ENV_VAR:default_value}
syntax for environment variable substitution.
public class MyAppFeatures extends SwitcherContext {
@SwitcherKey
public static final String FEATURE_NEW_UI = "FEATURE_NEW_UI";
@SwitcherKey
public static final String FEATURE_PREMIUM = "FEATURE_PREMIUM";
}
This approach provides more flexibility and is ideal for applications requiring dynamic configuration.
public class MyAppFeatures extends SwitcherContextBase {
@SwitcherKey
public static final String FEATURE_NEW_UI = "FEATURE_NEW_UI";
// Configure programmatically
static {
configure(ContextBuilder.builder()
.context(MyAppFeatures.class.getName())
.apiKey("YOUR_API_KEY")
.url("https://api.switcherapi.com")
.domain("MY_DOMAIN")
.component("my-application")
.environment("default"));
initializeClient();
}
}
@ConfigurationProperties(prefix = "switcher")
public class MySwitcherConfig extends SwitcherContextBase {
@SwitcherKey
public static final String FEATURE_NEW_UI = "FEATURE_NEW_UI";
@Override
@PostConstruct
public void configureClient() {
// Add any pre-configuration logic here
super.configureClient();
// Add any post-configuration logic here
}
}
// Load from custom properties file
MyAppFeatures.loadProperties("switcherapi-test");
Feature flags must follow specific conventions for proper functionality:
public class MyAppFeatures extends SwitcherContext {
// β
Correct: public static final String
@SwitcherKey
public static final String FEATURE_NEW_UI = "FEATURE_NEW_UI";
@SwitcherKey
public static final String FEATURE_PREMIUM_ACCESS = "FEATURE_PREMIUM_ACCESS";
// β Incorrect examples:
// private static final String WRONG = "WRONG"; // Not public
// public static String WRONG2 = "WRONG2"; // Not final
// public final String WRONG3 = "WRONG3"; // Not static
}
Why these conventions matter:
public
: Accessible from other parts of your applicationstatic
: No need to instantiate the class to access the constantfinal
: Prevents accidental modification during runtime
You can also name your feature flag attributes differently, but ensure the values match those defined in Switcher API.
// Simple boolean check
Switcher switcher = MyAppFeatures.getSwitcher(FEATURE_NEW_UI);
if (switcher.isItOn()) {
// Feature is enabled
}
Switcher switcher = MyAppFeatures.getSwitcher(FEATURE_NEW_UI);
SwitcherResult result = switcher.submit();
if (result.isItOn()) {
System.out.println("Feature enabled: " + result.getReason());
// Access additional metadata if needed
MyMetadata metadata = result.getMetadata(MyMetadata.class);
}
List<Entry> entries = new ArrayList<>();
entries.add(Entry.of(StrategyValidator.DATE, "2024-01-01"));
entries.add(Entry.of(StrategyValidator.TIME, "14:00"));
switcher.prepareEntry(entries);
boolean isEnabled = switcher.isItOn();
import static com.example.MyAppFeatures.*;
boolean isEnabled = getSwitcher(FEATURE_PREMIUM_ACCESS)
.checkValue("premium_user")
.checkNetwork("192.168.1.0/24")
.checkDate("2024-01-01")
.isItOn();
Switcher switcher = getSwitcher(FEATURE_NEW_UI)
.keepExecutions() // Enable execution tracking
.checkValue("user_type");
switcher.isItOn();
// Access the last execution result
SwitcherResult lastResult = switcher.getLastExecutionResult();
System.out.println("Last execution reason: " + lastResult.getReason());
// Clear history when needed
switcher.flushExecutions();
// Execute asynchronously with 1-second throttle
// Returns cached result if called within throttle period
boolean isEnabled = switcher.throttle(1000).isItOn();
Default mode that communicates directly with Switcher API.
MyAppFeatures.configure(ContextBuilder.builder()
.url("https://api.switcherapi.com")
.apiKey("YOUR_API_KEY")
.domain("MY_DOMAIN")
.component("my-app"));
MyAppFeatures.initializeClient();
Use Cases:
- Real-time feature flag updates
- A/B testing with immediate changes
- Centralized configuration management
Uses local snapshot files without API communication.
MyAppFeatures.configure(ContextBuilder.builder()
.local(true)
.snapshotLocation("./src/main/resources/snapshots"));
MyAppFeatures.initializeClient();
Use Cases:
- Offline environments
- High-performance scenarios where API latency is critical
- Development and testing environments
Combines remote and local capabilities for optimal flexibility.
// Force specific switcher to resolve remotely even in local mode
switcher.forceRemote().isItOn();
MyAppFeatures.configure(ContextBuilder.builder()
.url("https://api.switcherapi.com")
.apiKey("YOUR_API_KEY")
.domain("MY_DOMAIN")
.local(true)
.snapshotAutoLoad(true)
.snapshotAutoUpdateInterval("30s") // Check for updates every 30 seconds
.component("my-app"));
MyAppFeatures.initializeClient();
// Optional: Schedule with callback for monitoring
MyAppFeatures.scheduleSnapshotAutoUpdate("30s", new SnapshotCallback() {
@Override
public void onSnapshotUpdate(long version) {
logger.info("Snapshot updated to version: {}", version);
}
@Override
public void onSnapshotUpdateError(Exception e) {
logger.error("Failed to update snapshot: {}", e.getMessage());
}
});
Monitor snapshot files for external changes:
// Start watching for file changes
MyAppFeatures.watchSnapshot();
// Stop watching when no longer needed
MyAppFeatures.stopWatchingSnapshot();
Or enable during initialization:
MyAppFeatures.configure(ContextBuilder.builder()
.snapshotWatcher(true)
.snapshotLocation("./src/main/resources/snapshots"));
// Check if remote snapshot is newer than local
boolean hasUpdates = MyAppFeatures.validateSnapshot();
if (hasUpdates) {
logger.info("New snapshot version available");
}
MyAppFeatures.configure(ContextBuilder.builder()
.snapshotAutoUpdateInterval("5m") // Check every 5 minutes
.snapshotLocation("./src/main/resources/snapshots"));
Automatically fall back to cached results when API is unavailable:
MyAppFeatures.configure(ContextBuilder.builder()
.silentMode("30s") // Retry API calls every 30 seconds when failing
.url("https://api.switcherapi.com")
// ... other config
);
Time formats supported:
5s
- 5 seconds2m
- 2 minutes1h
- 1 hour
MyAppFeatures.configure(ContextBuilder.builder()
.timeoutMs(5000) // 5 second timeout
.poolConnectionSize(5) // 5 concurrent connections
// ... other config
);
@Test
void testFeatureEnabled() {
// Force switcher to return specific value
SwitcherBypass.assume(FEATURE_NEW_UI, true);
assertTrue(myService.usesNewUI());
// Reset to original behavior
SwitcherBypass.forget(FEATURE_NEW_UI);
}
@Test
void testWithConditions() {
Switcher switcher = MyAppFeatures.getSwitcher(FEATURE_PREMIUM_ACCESS)
.checkValue("user_type");
// Assume true only when specific condition is met
SwitcherBypass.assume(FEATURE_PREMIUM_ACCESS, true)
.when(StrategyValidator.VALUE, "premium");
assertTrue(switcher.isItOn());
}
@SwitcherTest(key = FEATURE_NEW_UI, result = true)
void testNewUIFeature() {
// FEATURE_NEW_UI will return true during this test
assertTrue(myService.usesNewUI());
// Automatically resets after test completion
}
@SwitcherTest(switchers = {
@SwitcherTestValue(key = FEATURE_NEW_UI, result = true),
@SwitcherTestValue(key = FEATURE_PREMIUM_ACCESS, result = false)
})
void testMultipleFeatures() {
assertTrue(myService.usesNewUI());
assertFalse(myService.hasPremiumAccess());
}
@SwitcherTest(key = FEATURE_NEW_UI, abTest = true)
void testFeatureABTesting() {
// Test passes regardless of switcher result
// Useful for testing both code paths
myService.handleUILogic();
}
@SwitcherTest(
key = FEATURE_PREMIUM_ACCESS,
result = true,
when = @SwitcherTestWhen(value = "premium_user")
)
void testPremiumFeature() {
// Test with specific input conditions
assertTrue(myService.checkPremiumAccess("premium_user"));
}
Validate all switcher keys are properly configured:
@Test
void validateSwitcherConfiguration() {
// Throws exception if any switcher key is not found
assertDoesNotThrow(() -> MyAppFeatures.checkSwitchers());
}
Enable automatic validation during startup:
MyAppFeatures.configure(ContextBuilder.builder()
.checkSwitchers(true) // Validate on initialization
// ... other config
);
Switcher Client fully supports GraalVM Native Image compilation:
@ConfigurationProperties
public class MyNativeAppFeatures extends SwitcherContextBase {
public static final String FEATURE_NEW_UI = "FEATURE_NEW_UI";
public static final String FEATURE_PREMIUM = "FEATURE_PREMIUM";
@Override
@PostConstruct
protected void configureClient() {
super.registerSwitcherKeys(FEATURE_NEW_UI, FEATURE_PREMIUM);
super.configureClient();
}
}
- π Switcher Tutorials - Complete code examples and tutorials
- π Switcher API Documentation - Backend API documentation
- π¬ Join our Slack - Community support and discussions
- π Report Issues - Bug reports and feature requests