Hey guys. Has anyone here tried to integrate growt...
# ask-questions
d
Hey guys. Has anyone here tried to integrate growth book sdk in Spring + Kotlin server? The example on official documentation does not look thread safe https://docs.growthbook.io/lib/kotlin-jvm#spring-boot
Copy code
@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
        )
    }
so I guess it’s basically not possible to use growthbook sdk in kotlin server unless i create growthbook instance for every request
am I missing something?
f
hello, the sample we provided in the docs isn't thread safe. We're in the process of making Kotlin sdk instance it safe for concurrent usecases and it isn't 100% thread-safe. That said, there's slightly uncomfortable way to make it thread-safe and use a singleton Growthbook sdk instance and create a standalone evaluator instance for each request. Here's an example:
Copy code
import 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:
Copy code
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.
let me know if this helps.
d
I’ll try it out. thanks for the suggestion
👍 1