google-site-verification: google61fe8ba583a51912.html
Dynamic Feature Control: Implementing Robust Feature Toggles in Java Applications

Feature toggles (or feature flags) are a powerful technique that allows you to modify system behavior without changing code. They enable gradual rollouts, A/B testing, emergency kill switches, and environment-specific functionality. Let's explore how to implement robust feature toggles in Java applications using various configuration strategies.

Architecture Overview

Feature Toggle Config → Toggle Manager → Feature Service → Application Code ↑ (Dynamic Sources: File, Database, ConfigMap, Redis)

Strategy 1: Spring Boot Configuration-Based Toggles

Dependencies

<!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>

Basic Toggle Configuration

// src/main/java/com/company/feature/FeatureConfig.java @Configuration @ConfigurationProperties(prefix = "features") @Data public class FeatureConfig { private Map<String, Boolean> toggles = new HashMap<>(); private Map<String, PercentageToggle> percentageToggles = new HashMap<>(); private Map<String, String> variantToggles = new HashMap<>(); @Data public static class PercentageToggle { private int percentage; private boolean defaultWhenDisabled = false; } }

Application Properties

# application.yml features: toggles: new-payment-gateway: true advanced-reporting: false dark-mode: true social-login: false percentage-toggles: beta-feature: percentage: 10 default-when-disabled: false gradual-rollout: percentage: 50 default-when-disabled: true variant-toggles: theme-color: "blue" pricing-tier: "premium" api-version: "v2" management: endpoints: web: exposure: include: features,health,info

Strategy 2: Advanced Feature Toggle Service

Core Feature Toggle Interface

// src/main/java/com/company/feature/FeatureToggleService.java public interface FeatureToggleService { boolean isEnabled(String featureName); boolean isEnabled(String featureName, String userId); String getVariant(String featureName); String getVariant(String featureName, String userId); void refreshToggles(); Map<String, Object> getAllToggles(); }

Implementation with Refreshable Configuration

// src/main/java/com/company/feature/ConfigFeatureToggleService.java @Service @Slf4j public class ConfigFeatureToggleService implements FeatureToggleService { private final FeatureConfig featureConfig; private final ObjectMapper objectMapper; private final Random random; // Cache for user-based consistent decisions private final Map<String, Map<String, Boolean>> userFeatureCache = new ConcurrentHashMap<>(); public ConfigFeatureToggleService(FeatureConfig featureConfig, ObjectMapper objectMapper) { this.featureConfig = featureConfig; this.objectMapper = objectMapper; this.random = new Random(); } @Override public boolean isEnabled(String featureName) { return isEnabled(featureName, null); } @Override public boolean isEnabled(String featureName, String userId) { // Check simple boolean toggles first Boolean simpleToggle = featureConfig.getToggles().get(featureName); if (simpleToggle != null) { return simpleToggle; } // Check percentage-based toggles FeatureConfig.PercentageToggle percentageToggle = featureConfig.getPercentageToggles().get(featureName); if (percentageToggle != null) { return isPercentageEnabled(featureName, percentageToggle, userId); } log.warn("Feature toggle not found: {}", featureName); return false; } private boolean isPercentageEnabled(String featureName, FeatureConfig.PercentageToggle toggle, String userId) { if (userId != null) { // Consistent behavior for same user return getUserPercentageDecision(featureName, toggle, userId); } else { // Random decision for anonymous users return random.nextInt(100) < toggle.getPercentage(); } } private boolean getUserPercentageDecision(String featureName, FeatureConfig.PercentageToggle toggle, String userId) { return userFeatureCache .computeIfAbsent(userId, k -> new HashMap<>()) .computeIfAbsent(featureName, k -> { // Deterministic based on user ID int hash = Math.abs(userId.hashCode()); return (hash % 100) < toggle.getPercentage(); }); } @Override public String getVariant(String featureName) { return getVariant(featureName, null); } @Override public String getVariant(String featureName, String userId) { String variant = featureConfig.getVariantToggles().get(featureName); return variant != null ? variant : "default"; } @Override public void refreshToggles() { userFeatureCache.clear(); log.info("Feature toggle cache cleared"); } @Override public Map<String, Object> getAllToggles() { Map<String, Object> allToggles = new HashMap<>(); allToggles.put("booleanToggles", featureConfig.getToggles()); allToggles.put("percentageToggles", featureConfig.getPercentageToggles()); allToggles.put("variantToggles", featureConfig.getVariantToggles()); return allToggles; } }

Strategy 3: Dynamic Feature Toggles with Database Backend

Database Schema

-- feature_toggles.sql CREATE TABLE feature_toggles ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL UNIQUE, enabled BOOLEAN DEFAULT FALSE, percentage INT DEFAULT 0, variant VARCHAR(100), target_users JSON, start_time TIMESTAMP NULL, end_time TIMESTAMP NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE feature_toggle_audit ( id BIGINT AUTO_INCREMENT PRIMARY KEY, feature_name VARCHAR(255) NOT NULL, user_id VARCHAR(255), enabled BOOLEAN, accessed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );

JPA Entity and Repository

// src/main/java/com/company/feature/entity/FeatureToggle.java @Entity @Table(name = "feature_toggles") @Data @EntityListeners(AuditingEntityListener.class) public class FeatureToggle { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true, nullable = false) private String name; private Boolean enabled = false; private Integer percentage = 0; private String variant; @Column(columnDefinition = "JSON") private String targetUsers; // JSON array of user IDs private Instant startTime; private Instant endTime; @CreatedDate private Instant createdAt; @LastModifiedDate private Instant updatedAt; @Transient public List<String> getTargetUserList() { if (targetUsers == null || targetUsers.trim().isEmpty()) { return Collections.emptyList(); } try { return Arrays.asList(new ObjectMapper().readValue(targetUsers, String[].class)); } catch (Exception e) { return Collections.emptyList(); } } } // Repository @Repository public interface FeatureToggleRepository extends JpaRepository<FeatureToggle, Long> { Optional<FeatureToggle> findByName(String name); List<FeatureToggle> findByEnabledTrue(); }

Advanced Database Feature Service

// src/main/java/com/company/feature/DatabaseFeatureToggleService.java @Service @Slf4j public class DatabaseFeatureToggleService implements FeatureToggleService { private final FeatureToggleRepository repository; private final FeatureToggleAuditRepository auditRepository; private final ObjectMapper objectMapper; // Local cache with TTL private final Cache<String, FeatureToggle> toggleCache = Caffeine.newBuilder() .expireAfterWrite(5, TimeUnit.MINUTES) .maximumSize(1000) .build(); public DatabaseFeatureToggleService(FeatureToggleRepository repository, FeatureToggleAuditRepository auditRepository, ObjectMapper objectMapper) { this.repository = repository; this.auditRepository = auditRepository; this.objectMapper = objectMapper; } @Override public boolean isEnabled(String featureName) { return isEnabled(featureName, null); } @Override public boolean isEnabled(String featureName, String userId) { FeatureToggle toggle = getToggle(featureName); if (toggle == null) { log.warn("Feature toggle not found: {}", featureName); auditAccess(featureName, userId, false); return false; } boolean enabled = evaluateToggle(toggle, userId); auditAccess(featureName, userId, enabled); return enabled; } private FeatureToggle getToggle(String featureName) { return toggleCache.get(featureName, key -> repository.findByName(key).orElse(null) ); } private boolean evaluateToggle(FeatureToggle toggle, String userId) { // Check time window if (!isWithinTimeWindow(toggle)) { return false; } // Check targeted users if (userId != null && !toggle.getTargetUserList().isEmpty()) { return toggle.getTargetUserList().contains(userId); } // Check percentage rollout if (toggle.getPercentage() > 0 && userId != null) { return isUserInPercentage(toggle.getName(), userId, toggle.getPercentage()); } // Simple boolean toggle return Boolean.TRUE.equals(toggle.getEnabled()); } private boolean isWithinTimeWindow(FeatureToggle toggle) { Instant now = Instant.now(); return (toggle.getStartTime() == null || !now.isBefore(toggle.getStartTime())) && (toggle.getEndTime() == null || !now.isAfter(toggle.getEndTime())); } private boolean isUserInPercentage(String featureName, String userId, int percentage) { // Consistent hashing for same user int hash = Math.abs((featureName + ":" + userId).hashCode()); return (hash % 100) < percentage; } private void auditAccess(String featureName, String userId, boolean enabled) { try { FeatureToggleAudit audit = new FeatureToggleAudit(); audit.setFeatureName(featureName); audit.setUserId(userId); audit.setEnabled(enabled); auditRepository.save(audit); } catch (Exception e) { log.warn("Failed to audit feature toggle access", e); } } @Override public void refreshToggles() { toggleCache.invalidateAll(); log.info("Feature toggle cache refreshed"); } @Scheduled(fixedRate = 60000) // Refresh every minute public void scheduledRefresh() { refreshToggles(); } }

Strategy 4: Kubernetes ConfigMap-Based Feature Toggles

ConfigMap Definition

# k8s/feature-toggles-configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: feature-toggles labels: app: java-app data: features.json: | { "toggles": { "new-ui": true, "beta-features": false, "payment-v2": true }, "percentageToggles": { "gradual-migration": { "percentage": 25, "defaultWhenDisabled": true } }, "variants": { "theme": "dark", "api-version": "v2" } }

ConfigMap Watcher Integration

// src/main/java/com/company/feature/ConfigMapFeatureManager.java @Component @Slf4j public class ConfigMapFeatureManager { private final FeatureToggleService featureService; private final ObjectMapper objectMapper; private volatile String currentConfigHash; public ConfigMapFeatureManager(FeatureToggleService featureService, ObjectMapper objectMapper) { this.featureService = featureService; this.objectMapper = objectMapper; } @EventListener public void onConfigMapUpdate(ConfigMapUpdateEvent event) { if (event.getConfigData().containsKey("features.json")) { try { String newConfig = event.getConfigData().get("features.json"); String newHash = calculateHash(newConfig); if (!newHash.equals(currentConfigHash)) { updateFeatures(newConfig); currentConfigHash = newHash; log.info("Feature toggles updated from ConfigMap"); } } catch (Exception e) { log.error("Failed to update features from ConfigMap", e); } } } private void updateFeatures(String configJson) throws Exception { FeatureConfig newConfig = objectMapper.readValue(configJson, FeatureConfig.class); // In a real implementation, you would update the feature service featureService.refreshToggles(); } private String calculateHash(String content) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hash = digest.digest(content.getBytes()); return Base64.getEncoder().encodeToString(hash); } catch (Exception e) { return content; // Fallback to content comparison } } }

Strategy 5: Feature Toggle Controllers and Management

REST API for Feature Management

// src/main/java/com/company/feature/FeatureToggleController.java @RestController @RequestMapping("/api/features") @Validated @Slf4j public class FeatureToggleController { private final FeatureToggleService featureService; public FeatureToggleController(FeatureToggleService featureService) { this.featureService = featureService; } @GetMapping public Map<String, Object> getAllFeatures() { return featureService.getAllToggles(); } @GetMapping("/{featureName}/enabled") public boolean isFeatureEnabled(@PathVariable String featureName, @RequestParam(required = false) String userId) { return featureService.isEnabled(featureName, userId); } @GetMapping("/{featureName}/variant") public String getFeatureVariant(@PathVariable String featureName, @RequestParam(required = false) String userId) { return featureService.getVariant(featureName, userId); } @PostMapping("/refresh") public ResponseEntity<Void> refreshFeatures() { featureService.refreshToggles(); return ResponseEntity.ok().build(); } }

Spring Boot Actuator Endpoint

// src/main/java/com/company/feature/FeatureToggleEndpoint.java @Component @Endpoint(id = "features") @Slf4j public class FeatureToggleEndpoint { private final FeatureToggleService featureService; public FeatureToggleEndpoint(FeatureToggleService featureService) { this.featureService = featureService; } @ReadOperation public Map<String, Object> features() { return featureService.getAllToggles(); } @WriteOperation public void refresh() { featureService.refreshToggles(); } }

Practical Usage Examples

1. Gradual Feature Rollout

// src/main/java/com/company/service/PaymentService.java @Service @Slf4j public class PaymentService { private final FeatureToggleService featureToggle; private final PaymentGatewayV1 gatewayV1; private final PaymentGatewayV2 gatewayV2; public PaymentService(FeatureToggleService featureToggle, PaymentGatewayV1 gatewayV1, PaymentGatewayV2 gatewayV2) { this.featureToggle = featureToggle; this.gatewayV1 = gatewayV1; this.gatewayV2 = gatewayV2; } public PaymentResult processPayment(PaymentRequest request, String userId) { if (featureToggle.isEnabled("payment-gateway-v2", userId)) { log.info("Using payment gateway V2 for user: {}", userId); return gatewayV2.process(request); } else { log.info("Using payment gateway V1 for user: {}", userId); return gatewayV1.process(request); } } }

2. A/B Testing

// src/main/java/com/company/service/RecommendationService.java @Service public class RecommendationService { private final FeatureToggleService featureToggle; private final AlgorithmA algorithmA; private final AlgorithmB algorithmB; public List<Product> getRecommendations(String userId) { String variant = featureToggle.getVariant("recommendation-algorithm", userId); switch (variant) { case "algorithm-a": return algorithmA.getRecommendations(userId); case "algorithm-b": return algorithmB.getRecommendations(userId); default: return algorithmA.getRecommendations(userId); } } public void trackRecommendationPerformance(String userId, String productId, boolean purchased) { String variant = featureToggle.getVariant("recommendation-algorithm", userId); // Send to analytics for A/B test evaluation analyticsService.trackConversion(variant, userId, productId, purchased); } }

3. Emergency Kill Switch

// src/main/java/com/company/service/DataExportService.java @Service public class DataExportService { private final FeatureToggleService featureToggle; @Async public CompletableFuture<ExportResult> exportUserData(String userId) { // Check kill switch before starting expensive operation if (!featureToggle.isEnabled("data-export-enabled")) { throw new FeatureDisabledException("Data export feature is currently disabled"); } return CompletableFuture.supplyAsync(() -> { // Perform expensive data export return performExport(userId); }); } }

Testing Feature Toggles

Unit Tests

// src/test/java/com/company/feature/FeatureToggleServiceTest.java @ExtendWith(MockitoExtension.class) class FeatureToggleServiceTest { @Mock private FeatureConfig featureConfig; @InjectMocks private ConfigFeatureToggleService featureService; @Test void testFeatureEnabled() { // Given Map<String, Boolean> toggles = new HashMap<>(); toggles.put("new-feature", true); when(featureConfig.getToggles()).thenReturn(toggles); // When boolean enabled = featureService.isEnabled("new-feature"); // Then assertThat(enabled).isTrue(); } @Test void testPercentageToggleConsistency() { // Given String userId = "user-123"; FeatureConfig.PercentageToggle toggle = new FeatureConfig.PercentageToggle(); toggle.setPercentage(50); Map<String, FeatureConfig.PercentageToggle> percentageToggles = new HashMap<>(); percentageToggles.put("gradual-rollout", toggle); when(featureConfig.getPercentageToggles()).thenReturn(percentageToggles); // When - multiple calls should return same result boolean firstCall = featureService.isEnabled("gradual-rollout", userId); boolean secondCall = featureService.isEnabled("gradual-rollout", userId); // Then assertThat(firstCall).isEqualTo(secondCall); } }

Integration Test

// src/test/java/com/company/feature/FeatureToggleIT.java @SpringBootTest @TestPropertySource(properties = { "features.toggles.new-ui=true", "features.percentage-toggles.beta-feature.percentage=25" }) class FeatureToggleIT { @Autowired private FeatureToggleService featureService; @Test void testFeatureToggleIntegration() { assertThat(featureService.isEnabled("new-ui")).isTrue(); assertThat(featureService.isEnabled("non-existent")).isFalse(); } }

Best Practices

  1. Toggle Lifetime Management
  • Regularly clean up old feature toggles
  • Use naming conventions (e.g., feat-*, kill-*, ops-*)
  • Document toggle purpose and ownership
  1. Monitoring and Observability
  • Log toggle evaluations for debugging
  • Track toggle usage metrics
  • Set up alerts for critical kill switches
  1. Security
  • Restrict toggle modification permissions
  • Validate toggle configurations
  • Audit toggle changes
  1. Performance
  • Cache toggle states appropriately
  • Use efficient data structures for user-based toggles
  • Minimize network calls for remote toggle services

Conclusion

Feature toggles are essential for modern Java applications, enabling:

  • Safe deployments with gradual rollouts
  • A/B testing and data-driven decisions
  • Operational control with kill switches
  • Environment-specific behavior without code changes

Choose the right strategy based on your needs:

  • Configuration-based: Simple, suitable for most applications
  • Database-backed: Dynamic, great for runtime management
  • External services: Enterprise-grade, full-featured

By implementing robust feature toggles, you gain unprecedented control over your application's behavior while maintaining stability and enabling rapid innovation.

Java Logistics, Shipping Integration & Enterprise Inventory Automation (Tracking, ERP, RFID & Billing Systems)

https://macronepal.com/blog/aftership-tracking-in-java-enterprise-package-visibility/
Explains how to integrate AfterShip tracking services into Java applications to provide real-time shipment visibility, delivery status updates, and centralized tracking across multiple courier services.

https://macronepal.com/blog/shipping-integration-using-fedex-api-with-java-for-logistics-automation/
Explains how to integrate the FedEx API into Java systems to automate shipping tasks such as creating shipments, calculating delivery costs, generating shipping labels, and tracking packages.

https://macronepal.com/blog/shipping-and-logistics-integrating-ups-apis-with-java-applications/
Explains UPS API integration in Java to enable automated shipping operations including rate calculation, shipment scheduling, tracking, and delivery confirmation management.

https://macronepal.com/blog/generating-and-reading-qr-codes-for-products-in-java/
Explains how Java applications generate and read QR codes for product identification, tracking, and authentication, supporting faster inventory handling and product verification processes.

https://macronepal.com/blog/designing-a-robust-pick-and-pack-workflow-in-java/
Explains how to design an efficient pick-and-pack workflow in Java warehouse systems, covering order processing, item selection, packaging steps, and logistics preparation to improve fulfillment efficiency.

https://macronepal.com/blog/rfid-inventory-management-system-in-java-a-complete-guide/
Explains how RFID technology integrates with Java applications to automate inventory tracking, reduce manual errors, and enable real-time stock monitoring in warehouses and retail environments.

https://macronepal.com/blog/erp-integration-with-odoo-in-java/
Explains how Java applications connect with Odoo ERP systems to synchronize inventory, orders, customer records, and financial data across enterprise systems.

https://macronepal.com/blog/automated-invoice-generation-creating-professional-excel-invoices-with-apache-poi-in-java/
Explains how to automatically generate professional Excel invoices in Java using Apache POI, enabling structured billing documents and automated financial record creation.

https://macronepal.com/blog/enterprise-financial-integration-using-quickbooks-api-in-java-applications/
Explains QuickBooks API integration in Java to automate financial workflows such as invoice management, payment tracking, accounting synchronization, and financial reporting.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper