A ZIO-native wrapper around the OpenFeature Java SDK for Scala 2.13 and Scala 3.
ZIO OpenFeature provides a type-safe, functional interface for feature flag evaluation using any OpenFeature-compatible provider. It wraps the OpenFeature Java SDK, giving you:
- Any OpenFeature Provider: Use LaunchDarkly, Flagsmith, CloudBees, Flipt, or any other OpenFeature provider
- Type Safety: Compile-time guarantees with the
FlagTypetype class - ZIO Integration: First-class effect handling, resource management, and fiber-local context
- Transactions: Scoped flag overrides with evaluation caching and tracking
- Hooks: Cross-cutting concerns for logging, metrics, and validation
- Scala 2.13+ or Scala 3.3+
- ZIO 2.1+
- Java 11+
| ZIO OpenFeature | OpenFeature Spec | OpenFeature Java SDK |
|---|---|---|
| 0.3.x | v0.8.0 | 1.20.2 |
This library implements the dynamic-context paradigm (server-side) of the OpenFeature specification. See Spec Compliance for details.
Replace <version> below with the version shown in the badge above.
// Core library (includes OpenFeature SDK)
libraryDependencies += "io.github.etacassiopeia" %% "zio-openfeature-core" % "<version>"
// Built-in providers: HOCON, env vars, caching wrapper (optional)
libraryDependencies += "io.github.etacassiopeia" %% "zio-openfeature-extras" % "<version>"
// For testing
libraryDependencies += "io.github.etacassiopeia" %% "zio-openfeature-testkit" % "<version>" % Testimport zio.*
import zio.openfeature.*
import zio.openfeature.testkit.*
object MyApp extends ZIOAppDefault:
val program = for
enabled <- FeatureFlags.boolean("my-feature", default = false)
_ <- ZIO.when(enabled)(Console.printLine("Feature is enabled!"))
yield ()
def run = program.provide(
Scope.default >>> TestFeatureProvider.layer(Map("my-feature" -> true))
)ZIO OpenFeature works with any OpenFeature Java SDK provider:
import zio.*
import zio.openfeature.*
import dev.openfeature.contrib.providers.flagd.FlagdProvider
object ProductionApp extends ZIOAppDefault:
val program = for
enabled <- FeatureFlags.boolean("new-checkout", default = false)
variant <- FeatureFlags.string("button-color", default = "blue")
yield (enabled, variant)
def run = program.provide(
Scope.default >>> FeatureFlags.fromProvider(new FlagdProvider())
)| Provider | Dependency |
|---|---|
| Optimizely | "dev.openfeature.contrib.providers" % "optimizely" % "x.y.z" |
| flagd | "dev.openfeature.contrib.providers" % "flagd" % "x.y.z" |
| LaunchDarkly | "dev.openfeature.contrib.providers" % "launchdarkly" % "x.y.z" |
| Flagsmith | "dev.openfeature.contrib.providers" % "flagsmith" % "x.y.z" |
| Flipt | "dev.openfeature.contrib.providers" % "flipt" % "x.y.z" |
See the OpenFeature ecosystem for all available providers.
// Boolean flags
val enabled = FeatureFlags.boolean("feature", default = false)
// String flags
val variant = FeatureFlags.string("variant", default = "control")
// Numeric flags
val limit = FeatureFlags.int("max-items", default = 100)
val rate = FeatureFlags.double("sample-rate", default = 0.1)
// Detailed evaluation with metadata
val details = FeatureFlags.booleanDetails("feature", default = false)
details.map { resolution =>
println(s"Value: ${resolution.value}")
println(s"Reason: ${resolution.reason}")
println(s"Variant: ${resolution.variant}")
}// Create context for targeting
val ctx = EvaluationContext("user-123")
.withAttribute("plan", "premium")
.withAttribute("country", "US")
// Evaluate with context
FeatureFlags.boolean("premium-feature", default = false, ctx)
// Set global context for all evaluations
FeatureFlags.setGlobalContext(ctx)
// Scope context to a block
FeatureFlags.withContext(ctx) {
FeatureFlags.boolean("feature", default = false)
}// Run code with flag overrides and evaluation tracking
val result = FeatureFlags.transaction(
overrides = Map("feature-a" -> true, "max-items" -> 50)
) {
for
a <- FeatureFlags.boolean("feature-a", default = false)
n <- FeatureFlags.int("max-items", default = 10)
yield (a, n)
}
result.map { txResult =>
println(s"Result: ${txResult.result}") // (true, 50)
println(s"Flags evaluated: ${txResult.flagCount}")
println(s"Overrides used: ${txResult.overrideCount}")
}// Add logging
FeatureFlags.addHook(FeatureHook.logging())
// Add metrics
FeatureFlags.addHook(FeatureHook.metrics { (key, duration, success) =>
ZIO.succeed(println(s"Flag $key evaluated in ${duration.toMillis}ms"))
})
// Validate context
FeatureFlags.addHook(FeatureHook.contextValidator(requireTargetingKey = true))// Track user actions
FeatureFlags.track("button-clicked")
// Track with details
val details = TrackingEventDetails(value = Some(99.99))
FeatureFlags.track("purchase", EvaluationContext("user-123"), details)// React to provider events
FeatureFlags.onProviderReady { metadata =>
ZIO.logInfo(s"Provider ${metadata.name} ready")
}
FeatureFlags.onConfigurationChanged { (flags, _) =>
ZIO.logInfo(s"Flags changed: ${flags.mkString(", ")}")
}| Module | Description |
|---|---|
| core | ZIO wrapper around OpenFeature SDK with FeatureFlags service |
| testkit | TestFeatureProvider for testing without external dependencies |
Full documentation: https://etacassiopeia.github.io/zio-openfeature/
- Getting Started - Installation and basic usage
- Architecture - Design and components
- Providers - Using OpenFeature providers
- Evaluation Context - Targeting and context hierarchy
- Hooks - Cross-cutting concerns
- Transactions - Overrides and tracking
- Testkit - Testing utilities
- Spec Compliance - OpenFeature specification compliance
Apache 2.0