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
- π¬ Join our Slack - Community support and discussions
- π Report Issues - Bug reports and feature requests
