Skip to content

Getting started

Install

Add the core module, plus whichever others you need, to build.sbt. Replace <version> with the latest release shown on Maven Central.

// The FeatureFlags service, on top of the OpenFeature Java SDK
libraryDependencies += "io.github.etacassiopeia" %% "zio-openfeature-core" % "<version>"

// Optional providers
libraryDependencies += "io.github.etacassiopeia" %% "zio-openfeature-extras"     % "<version>" // HOCON, env vars, caching, circuit breaker
libraryDependencies += "io.github.etacassiopeia" %% "zio-openfeature-ofrep"      % "<version>" // OpenFeature Remote Evaluation Protocol
libraryDependencies += "io.github.etacassiopeia" %% "zio-openfeature-optimizely" % "<version>" // Optimizely Feature Experimentation

// An in-memory provider for tests
libraryDependencies += "io.github.etacassiopeia" %% "zio-openfeature-testkit" % "<version>" % Test

The library is published for Scala 3.3 (3.3.8 or later) and Scala 2.13 (2.13.18 or later), needs ZIO 2.1.27 or later and Java 11 or later. The examples on this site use Scala 3 syntax.

Trying unreleased changes

Every commit on main publishes the next version with a -SNAPSHOT suffix (for example 1.2.1-SNAPSHOT after v1.2.0). Add the snapshots resolver to use it:

resolvers += "Sonatype Central Snapshots" at "https://central.sonatype.com/repository/maven-snapshots/"

Snapshots change without notice. Pin a released version for anything you ship.

Your first flag

FeatureFlags is a ZIO service. Ask it for a flag by key, with the value to use when the flag is not set. Here the service runs on the testkit's in-memory provider, so the program needs nothing else to run:

object FirstFlag extends ZIOAppDefault:

  val program: ZIO[FeatureFlags, FeatureFlagError, String] =
    for
      enabled <- FeatureFlags.boolean("new-checkout", default = false)
      message = if enabled then "new checkout" else "old checkout"
      _ <- ZIO.logInfo(message)
    yield message

  // An in-memory provider holding one flag
  def run = program.provide(TestFeatureProvider.layer(Map("new-checkout" -> true)))

Use a real provider

Your flags live in a flag service, which OpenFeature reaches through a provider. Any OpenFeature Java provider works: pass it to FeatureFlags.fromProvider, and the program itself does not change.

// Any OpenFeature Java provider: flagd, LaunchDarkly, Flagsmith, OFREP, Optimizely, ...
def withProvider(provider: FeatureProvider): ZIO[Any, Throwable | FeatureFlagError, String] =
  FirstFlag.program.provide(
    FeatureFlags.fromProvider(provider) // initializes the provider, shuts it down when the layer is released
  )

For flagd, for example, add "dev.openfeature.contrib.providers" % "flagd" % "<version>" and pass new FlagdProvider(). The OpenFeature ecosystem lists the providers. Wiring a provider covers the other ways to build the layer: async start-up, timeouts, named domains and several providers at once.

When a flag cannot be read

A flag may be missing, have the wrong type, or the provider may be down. Every evaluation method comes in two tiers, and you pick the one that fits the call site:

val strict: ZIO[FeatureFlags, FeatureFlagError, Boolean] =
  FeatureFlags.boolean("payments-enabled", default = false) // fails if the flag cannot be read

val lenient: ZIO[FeatureFlags, Nothing, Boolean] =
  FeatureFlags.booleanOrDefault("payments-enabled", default = false) // serves false instead

The typed tier fails with a FeatureFlagError saying what went wrong. Use it when a default would be the wrong answer. The total tier never fails: it serves your default and logs why. See Evaluating flags.

Declare a flag once

A key, a type and a default written out at every call site can drift apart. A FlagDef states them once:

object Flags:
  val NewCheckout = FlagDef("new-checkout", false, "the redesigned checkout flow")

val enabled: ZIO[FeatureFlags, FeatureFlagError, Boolean] = FeatureFlags.value(Flags.NewCheckout)

Definitions also work with your own enums and case classes. See Typed flags.

Where next

To Read
target a flag at a user, a request or a region Evaluation context
log, measure or validate every evaluation Hooks
pin flag values for one block of code, and see what it read Transactions
react to provider start-up, errors and flag changes Lifecycle and events
read flags from HOCON or env vars, or add caching and a circuit breaker Extras
test code that reads flags Testkit
see how it works inside Under the hood