/Docs

Spring Boot integration

This guide targets Spring Boot 3.2+ with Java 17 or later (Kotlin also supported). The avsb-spring-boot-starter dependency provides Spring Boot auto-configuration that registers the SDK as a singleton bean, adds a HandlerInterceptor for per-request context scoping, and exposes an AvsbClient bean ready for constructor injection. By the end you will be reading flags in controllers and Spring components with no boilerplate initialisation code.

1

Add the dependency

Maven:

pom.xml
xml
1<dependency>
2 <groupId>com.avsbhq</groupId>
3 <artifactId>avsb-spring-boot-starter</artifactId>
4 <version>1.0.0</version>
5</dependency>

Gradle (Kotlin DSL):

build.gradle.kts
kotlin
1implementation("com.avsbhq:avsb-spring-boot-starter:1.0.0")
2

Obtain your SDK key

Open your A vs B project, go to Settings → Environments, and copy the Server SDK key. Set it via an environment variable or in your application.yaml:

application.yaml
yaml
1avsb:
2 sdk-key: ${AVSB_SDK_KEY}
3
4 # Optional: polling interval in milliseconds (default 60000)
5 polling-interval: 60000
3

Configure context scoping (optional)

Implement the AvsbContextFactory interface and declare it as a Spring bean. The interceptor calls it once per request to build the EvalContext from the current HttpServletRequest.

RequestContextFactory.java
java
1import com.avsbhq.avsb.AvsbContextFactory;
2import com.avsbhq.avsb.core.EvalContext;
3import jakarta.servlet.http.HttpServletRequest;
4import org.springframework.stereotype.Component;
5import java.util.Map;
6
7@Component
8public class RequestContextFactory implements AvsbContextFactory {
9
10 @Override
11 public EvalContext fromRequest(HttpServletRequest request) {
12 String userId = request.getHeader("X-User-Id");
13 String plan = request.getHeader("X-User-Plan");
14 return EvalContext.user(
15 userId != null ? userId : "anon",
16 Map.of("plan", plan != null ? plan : "free")
17 );
18 }
19}
4

Inject AvsbClient into a controller

CheckoutController.java
java
1import com.avsbhq.avsb.AvsbClient;
2import com.avsbhq.avsb.core.Flag;
3import org.springframework.web.bind.annotation.GetMapping;
4import org.springframework.web.bind.annotation.RestController;
5import java.util.Map;
6
7@RestController
8public class CheckoutController {
9
10 private final AvsbClient avsb;
11
12 public CheckoutController(AvsbClient avsb) {
13 this.avsb = avsb;
14 }
15
16 @GetMapping("/checkout")
17 public Map<String, Object> show() {
18 Flag<Boolean> checkout = avsb.getBoolFlag("checkout-v2", false);
19 Flag<String> theme = avsb.getStringFlag("ui-theme", "default");
20
21 return Map.of(
22 "showNewCheckout", checkout.getValue(),
23 "theme", theme.getValue()
24 );
25 }
26}
Request-scoped bean
The AvsbClient bean injected by the auto-configuration is request-scoped when the interceptor is active. Injecting it as a constructor parameter in a @RestController (which is singleton-scoped by default) uses a CGLIB proxy — the same pattern Spring uses for HttpServletRequest injection.
5

Read a flag

java
1// Boolean — generic type inferred
2Flag<Boolean> flag = avsb.getBoolFlag("checkout-v2", false);
3if (flag.getValue()) { /* new experience */ }
4
5// String
6Flag<String> theme = avsb.getStringFlag("ui-theme", "default");
7
8// Number
9Flag<Double> timeout = avsb.getDoubleFlag("api-timeout-secs", 30.0);
10
11// JSON (Jackson-deserialised)
12Flag<PricingConfig> config = avsb.getJsonFlag("pricing-config",
13 PricingConfig.class, PricingConfig.getDefault());
14
15// Metadata
16System.out.printf("Source: %s, Variation: %s%n",
17 flag.getSource(), flag.getVariationKey());
6

Track an event

java
1avsb.track("purchase", TrackPayload.builder()
2 .value(149.99)
3 .property("currency", "usd")
4 .property("sku", "PRO-ANNUAL")
5 .build());
7

Identify a user

Inject AvsbServer when you need to create a scoped client for a different user mid-request (for example after a service-to-service token exchange):

java
1import com.avsbhq.avsb.AvsbServer;
2
3// In constructor: private final AvsbServer server;
4
5EvalContext ctx = EvalContext.user(user.getId(),
6 Map.of("plan", user.getPlan(), "orgId", user.getOrgId()));
7AvsbClient scoped = server.forUser(ctx);
8boolean showNew = scoped.getBoolFlag("checkout-v2", false).getValue();

Graceful shutdown

The auto-configuration registers the AvsbServer bean as a DisposableBean. Spring Boot's graceful shutdown sequence (enabled by default in Spring Boot 3.x) calls destroy() after in-flight requests complete — the SDK flushes pending events and stops the polling thread automatically. No extra configuration is required.

Testing

CheckoutControllerTest.java
java
1import com.avsbhq.avsb.testing.AvsbMockAutoConfiguration;
2import com.avsbhq.avsb.testing.MockAvsbClient;
3import org.junit.jupiter.api.Test;
4import org.springframework.beans.factory.annotation.Autowired;
5import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
6import org.springframework.context.annotation.Import;
7import org.springframework.test.web.servlet.MockMvc;
8import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
9import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
10
11@WebMvcTest(CheckoutController.class)
12@Import(AvsbMockAutoConfiguration.class)
13class CheckoutControllerTest {
14
15 @Autowired MockMvc mockMvc;
16 @Autowired MockAvsbClient mockAvsb;
17
18 @Test
19 void showsNewCheckout_whenFlagOn() throws Exception {
20 mockAvsb.flagOn("checkout-v2");
21
22 mockMvc.perform(get("/checkout"))
23 .andExpect(jsonPath("$.showNewCheckout").value(true));
24 }
25
26 @Test
27 void showsLegacy_whenFlagOff() throws Exception {
28 mockAvsb.flagOff("checkout-v2");
29
30 mockMvc.perform(get("/checkout"))
31 .andExpect(jsonPath("$.showNewCheckout").value(false));
32 }
33}

What's next