v0.4.8 (17 July 2026)
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 noEitherTbridges. Structured-concurrency combinators (firstSuccess,allSucceed/allSucceedAccumulating,withTimeout,bracketOutcome) keep typed failures in the value channel; defects stay on theVTaskchannel (#606) - Path-native resilience:
withRetry/withTimeout/withCircuitBreaker/withBulkheadnow form onewith*vocabulary across the family: instance-chained on the lazy carriers (IOPath,VTaskPath,VResultPath), static (step-as-Supplier) on the eagerEitherPath. Railway-aware: a businessLeftis a value (never retried, never trips the breaker), while typed overloads opt selected transient errors into retry and land timeouts/rejections asLefts (#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/reduceare total. The canonical companion toValidated(mirroring Cats'NonEmptyList/ValidatedNel):Path.validNel/invalidNelandValidated.validNel/invalidNelbake in its semigroup, dropping the manualSemigroups.list()argument. FullKind/Functor/Monad/Traversesupport, anassertThatNonEmptyListassertion, and Jackson support inhkj-spring. Purely additive (#549) - EitherOrBoth: The inclusive-or (
Ior/These) for a success that also carries non-fatal warnings: sealed overLeft/Right/Both, right-biased, with totalMaybe-returning accessors. AccumulatingflatMap, aKind/Kind2withBifunctor, an EitherOrBothPath railway, plushkj-springJackson 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, noSemigroupargument, and noKindceremony. Errors are locatedFieldErrors with composable paths; one shape across three carriers:Validated(strict),ValidationPath(railway), andEitherOrBoth(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/@GenerateMergeparsebuilt on it, locate up to 16 components; the ladder generates its ownTuple13..16so the For-comprehension arity stays independent at 12 (#626) - PathOps first-success NonEmptyList overloads: The five race/first-success combinators gain total
NonEmptyListoverloads beside the throwingListones, 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 totalbuildplus an accumulatingparsereturningValidated<NonEmptyList<FieldError>, User>.@MapFieldrenames, nesting,List/Optional/Mapcontainer lifting (map values lift like list elements, keys are identity), derived wire fields (aGetter-returningdefaultmethod fills a wire-only component onbuild), sealed-interface dispatch, and truthful emission tiers (asIso/asLens/ accumulatingparse). Every tier is law-checked against the publishedhkj-testharness (#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 aValidatedPrismleaf). 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>scompanion: per-variant factories, a typed context builder, and aneditContextwither. Context is records-as-schema (context.orderId(), notmap.get(...)); timestamps read from aTimeSourcefor deterministic tests (#610)
Optics
- ValidatedPrism: The smart-constructor optic for parse-don't-validate boundaries:
parsereturnsValidated<NonEmptyList<FieldError>, A>(every failure located),buildis total. Nested composition short-circuits while siblings accumulate; both round-trip laws ship asValidatedPrismLawsinhkj-test. Purely additive (#597) - Published optic-law harness:
hkj-testgainsorg.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:
@GenerateFocuscompanions emit the component name as a path segment, and every path type surfacessegments()/pathString()("customer.address.zip"), soEdit.parseIfPresentlocates parse failures automatically. Purely additive (#592) - Edits: Sparse, accumulating multi-edit over optics:
Edits.combinefolds pure edits into oneUpdate<S>;Edits.accumulateadds the validated REST-PATCHshape, reporting every bad field at once and applying the writes only if all validated.…IfPresenttreatsnullas absent. Purely additive (#582) - Update<S> and Monoids.update(): A named, composable update (
UnaryOperator<S>withidentity/andThen) and its monoid (theEndomonoid), the keystone underEdits. Purely additive (#591)
Tooling, Build, and Internals
- TimeSource:
java.time.Clocklifted into the effect world (system()/of(clock)/fixed(instant), lazynow()/nowAsync()), deterministic in tests; deliberately not namedClock.hkj-testgains aSteppableClockso 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/@ImportOpticserror paths and the standard for every future processor (#601) - Codegen on-ramp:
-parameterswired by both build plugins. The Gradle and Maven plugins now add-parametersautomatically, 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 theFilerfor correct incremental processing; generated output is byte-identical (#588) hkj-springdeserialisers resolve generic element types: TheEither/Validated/NonEmptyListdeserialisers now implement Jackson 3.x contextual resolution, so nested custom types round-trip instead of yieldingLinkedHashMapand aClassCastException(#578)- Worked-example hardening: The
example.orderandexample.marketshowcases were reviewed to demonstrate current HKJ functionality withhkj-testthroughout. Example- and documentation-only; no library API change