Skip to content

switcherapi/switcher-client-java

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

441 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

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

Circuit Breaker: Silent Mode

This feature allows you to specify how long the client SDK should attempt to restore connectivity in case of remote API failures.

When the API is unavailable, the SDK will automatically operate in silent mode, evaluating Switchers using a local snapshot. It is important to note that any Switcher Key configured must be able to resolve without external dependencies (e.g., Switcher Relay).

Make sure to configure the scheduled snapshot auto-update to keep the local snapshot up to date with the remote API.

Here is an example - in-memory snapshot with auto-update every 30 seconds:

MyAppFeatures.configure(ContextBuilder.builder()
    .context(MyAppFeatures.class.getName())
    .apiKey("YOUR_API_KEY")
    .url("https://api.switcherapi.com")
    .domain("MY_DOMAIN")
    .component("my-application")
    .silentMode("5m")
    .snapshotAutoUpdateInterval("30s")
);

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

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


AI Disclaimer

This project's core foundation was built and maintained by a human from day one. However, we have leveraged AI tools to assist in various aspects of the development process, such as troubleshooting, code optimization, and documentation. We have thoroughly reviewed and tested all AI-generated contributions to ensure they meet our quality standards and align with our project's goals. We are committed to transparency about our use of AI and will continue to disclose any significant AI contributions in the future.

External contributions from the community are equally valued and will be reviewed with the same standards, regardless of whether they were assisted by AI or not. We encourage all contributors to disclose their use of AI tools in their contributions to maintain transparency and foster trust within our community.

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

Languages