v0.4.8 (17 July 2026)

v0.4.8 on GitHub

Async, Typed Errors, and Resilience

The Path family gains a typed-error async carrier and a single resilience vocabulary.

  • VResultPath: A first-class railway for VTask<Either<E, A>> (async work that can fail with a typed domain error), speaking the family vocabulary (map/via/then, mapError/recover/recoverWith/bimap) with no EitherT bridges. Structured-concurrency combinators (firstSuccess, allSucceed/allSucceedAccumulating, withTimeout, bracketOutcome) keep typed failures in the value channel; defects stay on the VTask channel (#606)
  • Path-native resilience: withRetry/withTimeout/withCircuitBreaker/withBulkhead now form one with* vocabulary across the family: instance-chained on the lazy carriers (IOPath, VTaskPath, VResultPath), static (step-as-Supplier) on the eager EitherPath. Railway-aware: a business Left is a value (never retried, never trips the breaker), while typed overloads opt selected transient errors into retry and land timeouts/rejections as Lefts (#607)

Validation and Error Accumulation

A non-empty error channel, an inclusive-or, and open-arity validated assembly.

  • NonEmptyList: A list that encodes "at least one element" in the type, so head/last/reduce are total. The canonical companion to Validated (mirroring Cats' NonEmptyList/ValidatedNel): Path.validNel/invalidNel and Validated.validNel/invalidNel bake in its semigroup, dropping the manual Semigroups.list() argument. Full Kind/Functor/Monad/Traverse support, an assertThatNonEmptyList assertion, and Jackson support in hkj-spring. Purely additive (#549)
  • EitherOrBoth: The inclusive-or (Ior/These) for a success that also carries non-fatal warnings: sealed over Left/Right/Both, right-biased, with total Maybe-returning accessors. Accumulating flatMap, a Kind/Kind2 with Bifunctor, an EitherOrBothPath railway, plus hkj-spring Jackson support and a warnings-in-header return handler. Purely additive (#551, #583)
  • Open-arity accumulating assembly: Validated.fields()/accumulate() assemble a record from N validated fields (any arity to 16) with every error collected in declaration order, no Semigroup argument, and no Kind ceremony. Errors are located FieldErrors with composable paths; one shape across three carriers: Validated (strict), ValidationPath (railway), and EitherOrBoth (tolerant) (#581)
  • @GenerateAssembly: The codegen layer for the above: annotate a record and get a same-package UserAssembly.fields().name(v)…assemble() companion with one order-enforcing method per component; a component typed as another annotated record accepts its sub-companion's result directly (#586)
  • Assembly arity ceiling: the shared accumulate()/fields() ladder, and the @GenerateMapping/@GenerateMerge parse built on it, locate up to 16 components; the ladder generates its own Tuple13..16 so the For-comprehension arity stays independent at 12 (#626)
  • PathOps first-success NonEmptyList overloads: The five race/first-success combinators gain total NonEmptyList overloads beside the throwing List ones, so a statically-known competitor set needs no empty guard (#579, #585)

Record Mapping and Typed-Error Codegen

Three annotation processors for the record↔DTO boundary.

  • @GenerateMapping: Annotate interface UserMapping extends MappingSpec<User, UserDto> and the processor generates a total build plus an accumulating parse returning Validated<NonEmptyList<FieldError>, User>. @MapField renames, nesting, List/Optional/Map container lifting (map values lift like list elements, keys are identity), derived wire fields (a Getter-returning default method fills a wire-only component on build), sealed-interface dispatch, and truthful emission tiers (asIso / asLens / accumulating parse). Every tier is law-checked against the published hkj-test harness (#600)
  • @GenerateMerge: The forward-only assembler: annotate an interface whose one method declares DashboardDto assemble(User, Account, Settings) and the processor fills each target component from the same-named source (identity or through a ValidatedPrism leaf). Ambiguous or unfilled components are what/why/fix compile errors; no inverse is generated (#613)
  • @GenerateErrorEnvelope: Each variant of a sealed error hierarchy declares only its domain fields plus one ErrorEnvelope<C> component, and the processor generates the <Name>s companion: per-variant factories, a typed context builder, and an editContext wither. Context is records-as-schema (context.orderId(), not map.get(...)); timestamps read from a TimeSource for deterministic tests (#610)

Optics

  • ValidatedPrism: The smart-constructor optic for parse-don't-validate boundaries: parse returns Validated<NonEmptyList<FieldError>, A> (every failure located), build is total. Nested composition short-circuits while siblings accumulate; both round-trip laws ship as ValidatedPrismLaws in hkj-test. Purely additive (#597)
  • Published optic-law harness: hkj-test gains org.higherkindedj.optics.laws (IsoLaws, LensLaws, PrismLaws, AffineLaws, TraversalLaws), so users can law-test hand-written optics; failures name the violated law with the offending values. Purely additive (#596)
  • Optic-path labelling: @GenerateFocus companions emit the component name as a path segment, and every path type surfaces segments()/pathString() ("customer.address.zip"), so Edit.parseIfPresent locates parse failures automatically. Purely additive (#592)
  • Edits: Sparse, accumulating multi-edit over optics: Edits.combine folds pure edits into one Update<S>; Edits.accumulate adds the validated REST-PATCH shape, reporting every bad field at once and applying the writes only if all validated. …IfPresent treats null as absent. Purely additive (#582)
  • Update<S> and Monoids.update(): A named, composable update (UnaryOperator<S> with identity/andThen) and its monoid (the Endo monoid), the keystone under Edits. Purely additive (#591)

Tooling, Build, and Internals

  • TimeSource: java.time.Clock lifted into the effect world (system()/of(clock)/fixed(instant), lazy now() / nowAsync()), deterministic in tests; deliberately not named Clock. hkj-test gains a SteppableClock so time-dependent code is exercised by moving the clock, not sleeping (#609)
  • Processor diagnostics: what / why / fix. Annotation-processor errors now follow a shared three-part format (what is wrong, why, the exact fix), adopted across the @GenerateFocus/@GenerateLenses/@ImportOptics error paths and the standard for every future processor (#601)
  • Codegen on-ramp: -parameters wired by both build plugins. The Gradle and Maven plugins now add -parameters automatically, completing the one-line codegen setup (#602)
  • Annotation-processor hygiene: Every processor now reports SourceVersion.latestSupported() (no warnings on newer JDKs) and threads originating elements through the Filer for correct incremental processing; generated output is byte-identical (#588)
  • hkj-spring deserialisers resolve generic element types: The Either/Validated/NonEmptyList deserialisers now implement Jackson 3.x contextual resolution, so nested custom types round-trip instead of yielding LinkedHashMap and a ClassCastException (#578)
  • Worked-example hardening: The example.order and example.market showcases were reviewed to demonstrate current HKJ functionality with hkj-test throughout. Example- and documentation-only; no library API change

Previous: v0.4.9 Next: v0.4.7