nexus-kotlin-openfeature artifact wraps NexusClient behind the
OpenFeature Java SDK standard. It is published separately so teams that
do not use OpenFeature do not pull in the extra dependency.
Latest release: v0.15.1 (OpenFeature Java SDK 1.22.0)
Installation
nexus-kotlin-sdk, so the SDK resolves with it - you
do not have to list it separately (though doing so is harmless).
Registration
NexusClient.create does before returning.
Usage
Evaluation behaviour
Boolean flags report a missing flag
A flag that is not in the snapshot yieldsFLAG_NOT_FOUND with the caller’s default, rather than
the default with a STATIC reason. NexusClient.getFlag alone could not express this - it
collapses “absent” and “off” into one boolean, so every miss looked like a deliberate false. The
provider uses findFlag, which returns null for a flag that does not exist.
Object configs are structured
A JSON object becomes an OpenFeatureStructure and an array a List<Value>, recursively:
asStructure() return null for every composite
config, which is the one thing an object evaluation is for.
Within a structure, an integer that fits stays an integer, so reading 3 back does not give
3.0. An integer too large for Value’s 32-bit slot is carried as its exact decimal text (read
it with asString()): the type’s only other numeric slot is a Double, which cannot hold every
larger integer exactly - it would answer 9007199254740992 for 9007199254740993 with a success
reason. A genuinely fractional number is a Double. JSON null becomes an empty Value.
An Integer resolution requires an exact Int
A fractional value and one outside Int range are both reported as TYPE_MISMATCH, for the
same reason: 3.14 is not 3, and Int.MAX_VALUE is not 9007199254740993 - either, returned with
a success reason, is a wrong number the caller has no way to detect. You get the default you passed,
plus the error code saying the config’s type is what to look at.
A whole-numbered value such as 5.0 is an integer, because JSON has a single number type. Read a
config too large for an Int with NexusClient.getLong, which carries it exactly.
Per-user targeting
(v0.12.0) When theEvaluationContext carries a targeting key, a boolean evaluation is resolved per user
through the Nexus AB Testing add-on: the flag’s rollout percentage and cohort rules are applied to
that identity. Without a targeting key the provider reads the synced snapshot, which is a pure
in-memory lookup.
Attributes are strings
Cohort conditions compare attribute values as strings (eq, neq, in), so only string
attributes are sent. A non-string one is left out of the evaluation and reported through the
provider’s logger at debug level, naming the attribute.
Only boolean evaluation is targeted
String, double, integer and object values come from configs, and a config has no per-user dimension in Nexus.Requests and caching
A targeted evaluation is a network call, where the snapshot path is a map read. Two things keep that affordable, and neither needs configuring:- One request serves every flag the snapshot knows for that user, so reading five flags while handling a request costs one round trip. A snapshot larger than the service’s 200-key limit is split into successive calls.
- Results are memoised per user and flag for 30 seconds, and concurrent evaluations for the
same user coalesce into a single request. A memo hit reports the
CACHEDreason.
The targeted path blocks the calling thread
getBooleanEvaluation is synchronous in the OpenFeature API while NexusClient.evaluateAB is a
suspend function, so the bridge runs it in runBlocking(Dispatchers.IO). The IO dispatcher is
deliberate: the call must not occupy a thread from the caller’s own dispatcher, which on a
saturated Dispatchers.Default could deadlock. Calling this from inside a coroutine still blocks
that coroutine’s thread - the OpenFeature Java API offers no suspending entry point - so call
NexusClient.evaluateAB directly if you need a non-blocking targeted evaluation.
Options
Each option is validated in the constructor, naming the option and the value, so an unusable
window surfaces where it is set instead of turning into unexplained load later. A
targetingTtl of zero is rejected rather than read as “no caching” - it would make every
evaluation a network call.
Reason codes
A failed targeted request never surfaces as an error. The OpenFeature specification requires that
evaluation not throw and that an abnormal execution return the caller’s default, so reporting an
error would hand back your default for a flag that is genuinely on - instead the snapshot value is
served with
STALE: real, but not freshly targeted for this user. No error message is populated,
because a STALE fallback is a normal execution.
A project without the AB Testing add-on gets STATIC from the snapshot, which is authoritative
for it, and the provider waits five minutes before asking again. That pause is a throttle, not a
latch: it expires on its own and any success clears it, so buying the add-on takes effect without
a restart.
SPLIT is never reported: the endpoint returns booleans only, so a rollout bucket and a cohort
match are indistinguishable here, and TARGETING_MATCH - “the result of a dynamic evaluation,
such as a rule or specific user-targeting” - covers both.
variant carries the string form of the resolved value for boolean, string, double and integer
resolutions, which is the convention across the Westyx Nexus SDK suite. An object resolution has
none.
Notes
- String type-checking uses
JsonPrimitive.isString, so a numeric config stored as a JSON number returnsTYPE_MISMATCHfor a string evaluation rather than a coerced string.
