CodecRegistry

zio.temporal.json.CodecRegistry
See theCodecRegistry companion object
final class CodecRegistry

Thread-safe (per-index under append-only use) registry that maps runtime types to the zio-json encoders/decoders that ZioJsonPayloadConverter uses to cross a Temporal boundary.

'''Concurrency contract.''' Each of the three indexes (byClass, byType, byRawClass) is individually safe for concurrent access — a hit after a completed register is always seen by every reader. Cross-index visibility ''during'' a single register call is '''not''' atomic: a concurrent lookup may transiently see one index updated while another hasn't yet observed the same codec. Under the stated append-only usage the only observable effect is a transient miss (retriable by the caller); no partial overwrite is possible.

The registry is indexed three ways:

  • By java.lang.Class on the '''encode''' path (byClass). Temporal hands the DataConverter only v.getClass, so we must look up the encoder from that. Only ground-type codecs go here; see register for the rationale on keeping parameterized types out.
  • By java.lang.reflect.Type on the '''decode''' path (byType). Temporal hands the DataConverter a fully parameterized type (from the stub's method signature) that carries generic arguments — so List[Foo] and List[Bar] are distinguishable here.
  • By raw java.lang.Class with all parameterized-type candidates collected in a list (byRawClass). This is the fallback the encode path uses for user-defined generic case classes like Triple[A, B, C] where neither a direct byClass hit nor the container-iteration escape hatch applies. When a single parameterized candidate exists for a given raw class, its encoder is used; when multiple candidates exist, encode fails with a clear error listing both registered parameterized types.

All three views are populated in a single call to register so they stay in sync. Registrations are append-only: once a (class, type, codec) triple is in, it does not change.

In normal use the registry is populated at client/worker-construction time by zio-temporal's macros walking each workflow and activity interface. Manual registration via register is available for edge cases (e.g. an ad-hoc untyped stub).

Attributes

Companion
object
Graph
Supertypes
class Object
trait Matchable
class Any

Members list

Value members

Concrete methods

inline def addInterface[I]: CodecRegistry

Walk the workflow or activity interface I at compile time and register a codec for every parameter and return type of every method annotated with @workflowMethod / @signalMethod / @queryMethod / @activityMethod.

Walk the workflow or activity interface I at compile time and register a codec for every parameter and return type of every method annotated with @workflowMethod / @signalMethod / @queryMethod / @activityMethod.

Compilation fails with a clear message if any of those types lacks a ZTemporalCodec in scope, so a worker or client that is missing runtime registrations cannot be assembled.

Returns this so calls can be chained:

 val registry = new CodecRegistry()
   .addInterface[PaymentWorkflow]
   .addInterface[PaymentActivity]

Inherited @workflowMethods are included, so SodaWorkflow extends ParameterizedWorkflow[Soda] correctly registers codecs for the parent's abstract method.

Attributes

def decoderForClassHierarchy(cls: Class[_]): JsonDecoder[_] | Null

Look up a decoder by the value's runtime class, walking the superclass chain and implemented interfaces to find a registered ancestor. Mirrors encoderForClass for the decode path: when a concrete subtype of a sealed hierarchy (e.g. ParameterizedWorkflowInput.Soda) reaches the converter but only the sealed parent was registered, the walk finds the parent codec. Queries byType since java.lang.Class[_] is a java.lang.reflect.Type and ground codecs are indexed there under their class key.

Look up a decoder by the value's runtime class, walking the superclass chain and implemented interfaces to find a registered ancestor. Mirrors encoderForClass for the decode path: when a concrete subtype of a sealed hierarchy (e.g. ParameterizedWorkflowInput.Soda) reaches the converter but only the sealed parent was registered, the walk finds the parent codec. Queries byType since java.lang.Class[_] is a java.lang.reflect.Type and ground codecs are indexed there under their class key.

Attributes

def decoderForType(t: Type): JsonDecoder[_] | Null

Look up a decoder by a parameterized Java type. Returns null for "not found" to avoid allocating an Option on the hot deserialization path.

Look up a decoder by a parameterized Java type. Returns null for "not found" to avoid allocating an Option on the hot deserialization path.

Attributes

def encoderForClass(cls: Class[_]): JsonEncoder[_] | Null

Look up an encoder by the value's runtime class. Returns null for "not found" to avoid allocating an Option on the hot serialization path.

Look up an encoder by the value's runtime class. Returns null for "not found" to avoid allocating an Option on the hot serialization path.

If there is no exact match for cls, walks the class's supertype chain — first the superclass chain, then the implemented interfaces — looking for a registered codec on any ancestor. This covers two common patterns:

  1. Scala collections expose concrete subclasses that differ from the registered static type. A non-empty List[A] has runtime class scala.collection.immutable.$colon$colon, while the caller registered scala.collection.immutable.List. The superclass walk resolves the registered base codec.
  2. A user may register a codec on an interface (sealed trait Shape) and serialize concrete implementations (Rectangle(...)). The interface walk finds the registered Shape codec even when the concrete class itself was never explicitly registered.

This matches the Jackson-era behaviour most users relied on.

Attributes

def register[A](codec: ZTemporalCodec[A]): this.type

Register a codec.

Register a codec.

Ground types (where codec.genericType == codec.klass) are indexed in byClass keyed on the runtime class for encode-side lookup and in byType keyed on the same class for decode-side symmetry.

Parameterized types (e.g. ZTemporalCodec[List[Foo]]) are indexed in byType keyed on the full ParameterizedType and in byRawClass keyed on the raw runtime class. They are ''not'' added to byClass because every List[X] erases to the same raw classOf[List] at runtime — indexing them there would let the last-registered List[X] silently overwrite every other, and encoding any List[_] value would pick up the wrong element encoder, producing corrupted JSON.

The byRawClass side-index keeps the full set of parameterized candidates per raw class so the encode path can choose deterministically. For well-known containers (List, Vector, Set, Map, Option) the converter recurses into elements and never consults byRawClass. For user-defined generics (Triple[A, B, C]) the fallback is used only when exactly one parameterized instantiation is registered for the raw class; multiple candidates produce a clear encode-time error. See ZioJsonPayloadConverter.encodeValue.

Attributes

def registeredClassNames: Iterable[String]

For diagnostics: runtime classes indexed in the encode path.

For diagnostics: runtime classes indexed in the encode path.

Attributes

def registeredTypeNames: Iterable[String]

For diagnostics: human-readable list of registered types.

For diagnostics: human-readable list of registered types.

Attributes

def size: Int

Number of registered types.

Number of registered types.

Attributes