Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add featbit/featbit-skills --skill "featbit-java-sdk"
Install specific skill from multi-skill repository
# Description
Expert guidance for integrating FeatBit Java Server-Side SDK in backend applications. Use for Spring Boot, Servlet, or any Java server application. Not for Android - use Android SDK for mobile.
# SKILL.md
name: featbit-java-sdk
description: Expert guidance for integrating FeatBit Java Server-Side SDK in backend applications. Use for Spring Boot, Servlet, or any Java server application. Not for Android - use Android SDK for mobile.
appliesTo:
- "/*.java"
- "/pom.xml"
- "/build.gradle"
- "/src/main//*.java"
- "/application.properties"
- "**/application.yml"
FeatBit Java Server SDK Expert
Expert knowledge for integrating FeatBit Server-Side SDK in Java applications.
Source: https://github.com/featbit/featbit-java-sdk
⚠️ Important: This is a server-side SDK for multi-user systems (web servers, APIs). Not for Android apps - use Android SDK for mobile.
Data Synchronization
- WebSocket for real-time sync with FeatBit server
- Data stored in memory by default
- Changes pushed to SDK in <100ms average
- Auto-reconnects after internet outage
Installation
Maven
<dependencies>
<dependency>
<groupId>co.featbit</groupId>
<artifactId>featbit-java-sdk</artifactId>
<version>1.4.4</version>
</dependency>
</dependencies>
Gradle
implementation 'co.featbit:featbit-java-sdk:1.4.4'
Prerequisites
- Environment Secret: How to get
- SDK URLs: How to get
Quick Start
import co.featbit.commons.model.FBUser;
import co.featbit.commons.model.EvalDetail;
import co.featbit.server.FBClientImp;
import co.featbit.server.FBConfig;
import co.featbit.server.exterior.FBClient;
import java.io.IOException;
class Main {
public static void main(String[] args) throws IOException {
String envSecret = "<replace-with-your-env-secret>";
String streamUrl = "ws://localhost:5100";
String eventUrl = "http://localhost:5100";
FBConfig config = new FBConfig.Builder()
.streamingURL(streamUrl)
.eventURL(eventUrl)
.build();
FBClient client = new FBClientImp(envSecret, config);
if (client.isInitialized()) {
// The flag key to be evaluated
String flagKey = "use-new-algorithm";
// The user
FBUser user = new FBUser.Builder("bot-id")
.userName("bot")
.build();
// Evaluate a boolean flag for a given user
Boolean flagValue = client.boolVariation(flagKey, user, false);
System.out.printf("flag %s, returns %b for user %s%n", flagKey, flagValue, user.getUserName());
// Evaluate a boolean flag for a given user with evaluation detail
EvalDetail<Boolean> ed = client.boolVariationDetail(flagKey, user, false);
System.out.printf("flag %s, returns %b for user %s, reason: %s%n", flagKey, ed.getVariation(), user.getUserName(), ed.getReason());
}
// Close the client to ensure that all insights are sent out before the app exits
client.close();
System.out.println("APP FINISHED");
}
}
FBClient
Applications SHOULD instantiate a single FBClient instance for the lifetime of the application.
Bootstrapping
FBConfig config = new FBConfig.Builder()
.streamingURL(streamUrl)
.eventURL(eventUrl)
.startWaitTime(Duration.ofSeconds(10))
.build();
FBClient client = new FBClientImp(envSecret, config);
if(client.isInitialized()){
// the client is ready
}
Asynchronous Initialization
FBConfig config = new FBConfig.Builder()
.streamingURL(streamUrl)
.eventURL(eventUrl)
.startWaitTime(Duration.ZERO)
.build();
FBClient client = new FBClientImp(sdkKey, config);
// later, when you want to wait for initialization to finish:
boolean inited = client.getDataUpdateStatusProvider().waitForOKState(Duration.ofSeconds(10))
if (inited) {
// the client is ready
}
Spring Boot Integration
// FeatBitConfig.java
import co.featbit.server.FBClient;
import co.featbit.server.FBClientImp;
import co.featbit.server.FBConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PreDestroy;
import java.time.Duration;
@Configuration
public class FeatBitConfig {
@Value("${featbit.env-secret}")
private String envSecret;
@Value("${featbit.streaming-url}")
private String streamingUrl;
@Value("${featbit.event-url}")
private String eventUrl;
private FBClient fbClient;
@Bean
public FBClient fbClient() {
FBConfig config = new FBConfig.Builder()
.streamingURL(streamingUrl)
.eventURL(eventUrl)
.startWaitTime(Duration.ofSeconds(15))
.build();
fbClient = new FBClientImp(envSecret, config);
if (!fbClient.isInitialized()) {
throw new RuntimeException("FeatBit SDK failed to initialize");
}
return fbClient;
}
@PreDestroy
public void cleanup() {
if (fbClient != null) {
try {
fbClient.close();
} catch (Exception e) {
// Log error
}
}
}
}
// application.properties
featbit.env-secret=your-env-secret
featbit.streaming-url=wss://app-eval.featbit.co
featbit.event-url=https://app-eval.featbit.co
// FeatureService.java
import co.featbit.server.FBClient;
import co.featbit.commons.model.FBUser;
import org.springframework.stereotype.Service;
@Service
public class FeatureService {
private final FBClient fbClient;
public FeatureService(FBClient fbClient) {
this.fbClient = fbClient;
}
public boolean isFeatureEnabled(String userId, String flagKey) {
FBUser user = new FBUser.Builder(userId).build();
return fbClient.boolVariation(flagKey, user, false);
}
public <T> T getFeatureValue(String userId, String flagKey, T defaultValue) {
FBUser user = new FBUser.Builder(userId).build();
return fbClient.variation(flagKey, user, defaultValue);
}
}
// FeatureController.java
import co.featbit.commons.model.FBUser;
import co.featbit.server.FBClient;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class FeatureController {
private final FBClient fbClient;
public FeatureController(FBClient fbClient) {
this.fbClient = fbClient;
}
@GetMapping("/feature")
public ResponseEntity<Map<String, Object>> checkFeature(
@RequestHeader("X-User-Id") String userId) {
FBUser user = new FBUser.Builder(userId)
.userName("User " + userId)
.custom("role", "user")
.build();
boolean isEnabled = fbClient.boolVariation("new-feature", user, false);
if (!isEnabled) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(Map.of("error", "Feature not available"));
}
return ResponseEntity.ok(Map.of("data", "feature data"));
}
@PostMapping("/purchase")
public ResponseEntity<Map<String, Object>> purchase(
@RequestHeader("X-User-Id") String userId,
@RequestBody PurchaseRequest request) {
FBUser user = new FBUser.Builder(userId).build();
String variant = fbClient.variation("pricing-strategy", user, "standard");
double price = calculatePrice(request, variant);
PurchaseResult result = processPurchase(price);
if (result.isSuccess()) {
fbClient.trackMetric(user, "purchase-completed", price);
}
return ResponseEntity.ok(Map.of(
"success", result.isSuccess(),
"variant", variant,
"price", price
));
}
}
FBUser Builder
import co.featbit.commons.model.FBUser;
// Basic user
FBUser user = new FBUser.Builder("user-key-123")
.userName("John Doe")
.build();
// User with custom properties
FBUser user = new FBUser.Builder("user-key-456")
.userName("Bob Wilson")
.custom("role", "admin")
.custom("country", "US")
.custom("subscription", "premium")
.custom("age", 30)
.build();
Configuration Options
import java.time.Duration;
FBConfig config = new FBConfig.Builder()
// Required
.streamingURL("wss://app-eval.featbit.co")
.eventURL("https://app-eval.featbit.co")
// Optional
.startWaitTime(Duration.ofSeconds(15)) // Wait time for initialization
.offline(false) // Offline mode
.disableEvents(false) // Disable event sending
.build();
HTTP Configuration
import co.featbit.server.Factory;
import co.featbit.server.HttpConfigFactory;
import java.time.Duration;
HttpConfigFactory factory = Factory.httpConfigFactory()
.connectTime(Duration.ofMillis(3000))
.readTime(Duration.ofMillis(5000))
.writeTime(Duration.ofMillis(5000))
.httpProxy("my-proxy", 9000);
FBConfig config = new FBConfig.Builder()
.httpConfigFactory(factory)
.build();
Flag Evaluation
Boolean Variation
Boolean isEnabled = client.boolVariation("flag-key", user, false);
String Variation
String theme = client.variation("theme", user, "default");
Numeric Variations
// Double
Double percentage = client.doubleVariation("rollout-percentage", user, 0.0);
// Long
Long timeout = client.longVariation("timeout", user, 30000L);
// Integer
Integer maxRetries = client.intVariation("max-retries", user, 3);
JSON Variation
import com.google.gson.JsonObject;
JsonObject config = client.jsonVariation("config", user, new JsonObject());
String apiKey = config.get("apiKey").getAsString();
int maxConnections = config.get("maxConnections").getAsInt();
With Evaluation Detail
import co.featbit.commons.model.EvalDetail;
EvalDetail<String> detail = client.variationDetail("flag-key", user, "default");
System.out.println("Value: " + detail.getVariation());
System.out.println("Reason: " + detail.getReason());
System.out.println("Kind: " + detail.getKind());
Get All Flags
import co.featbit.commons.model.AllFlagStates;
AllFlagStates states = client.getAllLatestFlagsVariations(user);
Collection<String> flagKeys = states.getFlagKeys();
// Get specific flag values
EvalDetail<String> detail = states.getStringDetail("flag-key", "default");
String value = states.getString("flag-key", "default");
Event Tracking
// Simple event
client.trackMetric(user, "button-clicked");
// Event with numeric value
client.trackMetric(user, "purchase-completed", 99.99);
// For A/B testing
client.trackMetric(user, "conversion-event");
// Flush events manually
client.flush();
Flag Tracking (Listeners)
client.getFlagTracker().addFlagValueChangeListener(flagKey, user, event -> {
System.out.println("Flag " + event.getKey() + " changed");
System.out.println("Old value: " + event.getOldValue());
System.out.println("New value: " + event.getNewValue());
});
Servlet Integration
import co.featbit.server.FBClient;
import co.featbit.commons.model.FBUser;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class FeatureServlet extends HttpServlet {
private FBClient fbClient;
@Override
public void init() {
FBConfig config = new FBConfig.Builder()
.streamingURL(getServletContext().getInitParameter("featbit.streaming.url"))
.eventURL(getServletContext().getInitParameter("featbit.event.url"))
.build();
fbClient = new FBClientImp(
getServletContext().getInitParameter("featbit.env.secret"),
config
);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
String userId = req.getParameter("userId");
FBUser user = new FBUser.Builder(userId).build();
boolean isEnabled = fbClient.boolVariation("new-feature", user, false);
resp.setContentType("application/json");
resp.getWriter().write("{\"enabled\": " + isEnabled + "}");
}
@Override
public void destroy() {
if (fbClient != null) {
try {
fbClient.close();
} catch (Exception e) {
log("Error closing FeatBit client", e);
}
}
}
}
Offline Mode
FBConfig config = new FBConfig.Builder()
.streamingURL(streamUrl)
.eventURL(eventUrl)
.offline(true)
.build();
FBClient client = new FBClientImp(envSecret, config);
// Load flags from JSON file
String json = Resources.toString(
Resources.getResource("featbit-bootstrap.json"),
Charsets.UTF_8
);
if(client.initFromJsonFile(json)){
// the client is ready
}
Generate Bootstrap JSON
# Get current flags
curl -H "Authorization: <your-env-secret>" \
http://localhost:5100/api/public/sdk/server/latest-all > featbit-bootstrap.json
Best Practices
1. Single Client Instance
// ✅ Good: Use Dependency Injection
@Bean
public FBClient fbClient() {
return new FBClientImp(envSecret, config);
}
// ❌ Bad: Creating per request
@GetMapping("/feature")
public void checkFeature() {
FBClient client = new FBClientImp(...); // Don't do this!
}
2. Wait for Initialization
// ✅ Good: Wait and verify
if (!fbClient.isInitialized()) {
throw new RuntimeException("SDK not ready");
}
// ❌ Bad: Use without checking
FBClient client = new FBClientImp(...);
client.boolVariation(...); // May return defaults
3. Graceful Shutdown
@PreDestroy
public void cleanup() {
if (fbClient != null) {
fbClient.close(); // Flush events
}
}
4. Error Handling
try {
Boolean isEnabled = fbClient.boolVariation("flag-key", user, false);
// Use feature
} catch (Exception e) {
logger.error("Flag evaluation failed", e);
// Fall back to default
Boolean isEnabled = false;
}
Common Use Cases
Progressive Rollout
@GetMapping("/api/new-feature")
public ResponseEntity<Data> newFeature(@AuthenticationPrincipal User user) {
FBUser fbUser = new FBUser.Builder(user.getId()).build();
boolean useNewVersion = fbClient.boolVariation("new-api-version", fbUser, false);
if (useNewVersion) {
return ResponseEntity.ok(newVersionHandler());
} else {
return ResponseEntity.ok(oldVersionHandler());
}
}
A/B Testing
@GetMapping("/api/recommendations")
public List<Recommendation> getRecommendations(@AuthenticationPrincipal User user) {
FBUser fbUser = new FBUser.Builder(user.getId()).build();
String algorithm = fbClient.variation("recommendation-algorithm", fbUser, "default");
List<Recommendation> recommendations = generateRecommendations(algorithm);
// Track which algorithm was used
fbClient.trackMetric(fbUser, "algorithm-" + algorithm);
return recommendations;
}
Remote Configuration
JsonObject config = fbClient.jsonVariation("api-config", systemUser, defaultConfig);
int timeout = config.get("timeout").getAsInt();
int maxRetries = config.get("maxRetries").getAsInt();
int rateLimit = config.get("rateLimit").getAsInt();
// Use configuration
configureHttpClient(timeout, maxRetries, rateLimit);
Troubleshooting
SDK Not Initializing
FBClient client = new FBClientImp(envSecret, config);
if (!client.isInitialized()) {
// Check:
// 1. envSecret is correct
// 2. Network connectivity
// 3. Firewall rules for WebSocket
logger.error("FeatBit SDK failed to initialize");
}
Connection Issues
// Increase timeout
FBConfig config = new FBConfig.Builder()
.startWaitTime(Duration.ofSeconds(30))
.build();
Events Not Sent
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (fbClient != null) {
fbClient.close(); // Flush events
}
}));
Testing
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;
public class FeatureTest {
private static FBClient fbClient;
@BeforeAll
public static void setUp() {
FBConfig config = new FBConfig.Builder()
.streamingURL("wss://test-eval.featbit.co")
.eventURL("https://test-eval.featbit.co")
.build();
fbClient = new FBClientImp("test-env-secret", config);
}
@Test
public void testFeatureFlag() {
FBUser user = new FBUser.Builder("test-user").build();
Boolean isEnabled = fbClient.boolVariation("test-feature", user, false);
assertNotNull(isEnabled);
}
@AfterAll
public static void tearDown() {
if (fbClient != null) {
try {
fbClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
OpenFeature Provider
FeatBit also provides an OpenFeature provider for Java:
<dependency>
<groupId>co.featbit</groupId>
<artifactId>featbit-openfeature-provider-java-server</artifactId>
<version>1.0.0</version>
</dependency>
import dev.openfeature.sdk.OpenFeatureAPI;
import co.featbit.openfeature.FeatBitProvider;
FBConfig config = new FBConfig.Builder()
.streamingURL(STREAM_URL)
.eventURL(EVENT_URL)
.build();
// Synchronous
OpenFeatureAPI.getInstance().setProviderAndWait(new FeatBitProvider(ENV_SECRET, config));
// Asynchronous
OpenFeatureAPI.getInstance().setProvider(new FeatBitProvider(ENV_SECRET, config));
Additional Resources
- GitHub: https://github.com/featbit/featbit-java-sdk
- Maven Central: https://mvnrepository.com/artifact/co.featbit/featbit-java-sdk
- Examples: https://github.com/featbit/featbit-samples
- Documentation: https://docs.featbit.co/sdk-docs/server-side-sdks/java
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.