damp-receptionist-34609
12/11/2025, 1:25 PM@Configuration
class GrowthBookConfig {
@Bean
suspend fun growthBook(): GrowthBookSDK {
val growthBook = GBSDKBuilder(
apiKey = System.getenv("GROWTHBOOK_API_KEY"),
hostURL = "<https://cdn.growthbook.io/>",
attributes = mapOf("environment" to "production"),
networkDispatcher = NetworkDispatcherOkHttp()
).initialize()
growthBook.refreshCache()
return growthBook
}
}
@RestController
@RequestMapping("/api")
class FeatureController(private val growthBook: GrowthBookSDK) {
@GetMapping("/features/{userId}")
suspend fun getUserFeatures(@PathVariable userId: String): Map<String, Any?> {
// Set user-specific attributes
growthBook.setAttributes(mapOf(
"id" to userId,
"environment" to "production"
))
return mapOf(
"newDashboard" to growthBook.feature("new-dashboard").on,
"maxItems" to growthBook.feature("max-items").value,
"premiumFeatures" to growthBook.feature("premium-features").on
)
}damp-receptionist-34609
12/11/2025, 1:27 PMdamp-receptionist-34609
12/11/2025, 1:27 PMfreezing-postman-69602
12/11/2025, 6:59 PMimport com.sdk.growthbook.GrowthBookSDK
import com.sdk.growthbook.GBSDKBuilder
import com.sdk.growthbook.network.NetworkDispatcherOkHttp
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Service
import org.springframework.boot.ApplicationRunner // Required for startup logic
import org.springframework.boot.CommandLineRunner
@Configuration
class GrowthBookConfig {
// The Singleton Bean to manage feature data and configuration
@Bean
suspend fun featureCacheSdk(): GrowthBookSDK {
// IMPORTANT: Initialize with ONLY static/environment attributes
val growthBook = GBSDKBuilder(
apiKey = System.getenv("GROWTHBOOK_API_KEY"),
hostURL = "<https://cdn.growthbook.io/>",
attributes = mapOf("environment" to "production"), // Static attributes only
networkDispatcher = NetworkDispatcherOkHttp()
).initialize()
// Refresh cache on startup (suspending call is safe in a Spring @Bean)
growthBook.refreshCache()
return growthBook
}
}
// Helper Service to access the cache and create a new evaluator for each request
@Service
class GrowthBookService(private val featureCacheSdk: GrowthBookSDK) {
fun createEvaluator(userAttributes: Map<String, Any?>): GrowthBookSDK {
// 1. Get the latest, cached feature data from the singleton.
val cachedFeatures = featureCacheSdk.getFeatures()
// 2. Build a new, isolated SDK instance for the request.
return GBSDKBuilder(
// Use the cached features, avoiding a network call
initialFeatures = cachedFeatures,
// Use the request-specific attributes
attributes = userAttributes,
// The network dispatcher is required in the builder, but should be a safe placeholder
// since we are using cached features (or reuse the singleton's config).
networkDispatcher = NetworkDispatcherOkHttp()
).initializeWithoutWaitForCall() // Instantly initializes with features, no suspend
}
}
And finally, In the controller, you use the evaluator SDK for each request:
val evaluator: GrowthBookSDK = growthBookService.createEvaluator(userAttributes)
that way, you can isolate the mutable user state and still have the network calls to fetch features limited to the singleton instance.freezing-postman-69602
12/11/2025, 7:03 PMdamp-receptionist-34609
12/12/2025, 4:51 AM