Skip to content

switcherapi/switcher-client-java

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Switcher Client SDK
A Java SDK for Switcher API

Master CI Quality Gate Status Known Vulnerabilities License: MIT Maven Central Slack: Switcher-HQ


Switcher API: Java Client: Cloud-based Feature Flag API

Table of Contents

Overview

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.

Key Features

  • πŸ”§ 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

Installation

Maven Dependency

Add the following dependency to your pom.xml:

<dependency>
  <groupId>com.switcherapi</groupId>
  <artifactId>switcher-client</artifactId>
  <version>${switcher-client.version}</version>
</dependency>

Build from Source

mvn clean install

Version Compatibility

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

Quick Start

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
        }
    }
}

Configuration

Using SwitcherContext (Properties-based)

This approach automatically loads configuration from a properties file, ideal for applications with externalized configuration.

Step 1: Create Configuration File

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

Configuration Properties Reference

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.

Step 2: Define Feature Class

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";
}

Using SwitcherContextBase (Programmatic)

This approach provides more flexibility and is ideal for applications requiring dynamic configuration.

Basic Programmatic 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();
    }
}

Spring Boot Integration

@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
    }
}

Custom Properties File

// Load from custom properties file
MyAppFeatures.loadProperties("switcherapi-test");

Defining Feature Flags

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 application
  • static: No need to instantiate the class to access the constant
  • final: Prevents accidental modification during runtime

You can also name your feature flag attributes differently, but ensure the values match those defined in Switcher API.

Usage Patterns

1. Basic Flag Checking

// Simple boolean check
Switcher switcher = MyAppFeatures.getSwitcher(FEATURE_NEW_UI);
if (switcher.isItOn()) {
    // Feature is enabled
}

2. Detailed Result Information

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);
}

3. Strategy Validation with Input Parameters

Preparing Input Separately

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();

Fluent API Style (Recommended)

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();

4. Execution History Tracking

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();

5. Performance Optimization with Throttling

// Execute asynchronously with 1-second throttle
// Returns cached result if called within throttle period
boolean isEnabled = switcher.throttle(1000).isItOn();

Operating Modes

Remote Mode

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

Local Mode

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

Hybrid Mode

Combines remote and local capabilities for optimal flexibility.

Force Remote Resolution

// Force specific switcher to resolve remotely even in local mode
switcher.forceRemote().isItOn();

In-Memory Snapshots with Auto-Update

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());
    }
});

Advanced Features

Real-time Snapshot Management

File System Watcher

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"));

Manual Snapshot Validation

// Check if remote snapshot is newer than local
boolean hasUpdates = MyAppFeatures.validateSnapshot();
if (hasUpdates) {
    logger.info("New snapshot version available");
}

Automated Background Updates

MyAppFeatures.configure(ContextBuilder.builder()
    .snapshotAutoUpdateInterval("5m")  // Check every 5 minutes
    .snapshotLocation("./src/main/resources/snapshots"));

Performance Optimization

Silent Mode (Resilience)

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 seconds
  • 2m - 2 minutes
  • 1h - 1 hour

Connection Pooling

MyAppFeatures.configure(ContextBuilder.builder()
    .timeoutMs(5000)        // 5 second timeout
    .poolConnectionSize(5)  // 5 concurrent connections
    // ... other config
);

Testing

Built-in Test Utilities

SwitcherBypass for Unit Tests

@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());
}

JUnit 5 Integration

Single Switcher Test

@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
}

Multiple Switchers

@SwitcherTest(switchers = {
    @SwitcherTestValue(key = FEATURE_NEW_UI, result = true),
    @SwitcherTestValue(key = FEATURE_PREMIUM_ACCESS, result = false)
})
void testMultipleFeatures() {
    assertTrue(myService.usesNewUI());
    assertFalse(myService.hasPremiumAccess());
}

A/B Testing

@SwitcherTest(key = FEATURE_NEW_UI, abTest = true)
void testFeatureABTesting() {
    // Test passes regardless of switcher result
    // Useful for testing both code paths
    myService.handleUILogic();
}

Conditional Testing

@SwitcherTest(
    key = FEATURE_PREMIUM_ACCESS, 
    result = true,
    when = @SwitcherTestWhen(value = "premium_user")
)
void testPremiumFeature() {
    // Test with specific input conditions
    assertTrue(myService.checkPremiumAccess("premium_user"));
}

Smoke Testing

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
);

Native Image Support

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();
    }
}

Additional Resources


About

[Java] Switcher Client - Java SDK to work with Switcher API - Cloud-based Feature Flag

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Contributors 2

  •  
  •  

Languages