Release History

This page documents the evolution of Higher-Kinded-J from its initial release through to the current version. Each release builds on the foundations established by earlier versions, progressively adding type classes, monads, optics, and the Effect Path API.

What You'll Find

  • Detailed release notes for recent versions (0.3.0–0.4.11) with links to documentation
  • Summary release notes for earlier versions (pre-0.3.0)
  • Links to GitHub release pages for full changelogs

Recent Releases

0.4.11-SNAPSHOT (Latest)

In development. Release notes will be added here as changes land on main.

v0.4.10 (30 August 2026)

This release gives the mapper a stock vocabulary and takes every generating annotation through a generics pass: each one now emits source the consuming build compiles under -Werror, or explains at the declaration what it needs instead. The book gains a Mapping at the Boundary chapter with a law-checked capstone, and the house style it introduced now runs through the Optics and Resilience chapters.

Mapping at the Boundary (#670, #671, #672, #690, #752)

  • A chapter of its own: the record-mapping material becomes Mapping at the Boundary, covering basics, codecs, structure, tiers, beans and sparse PATCH, generics, merge and envelopes, injection and testing. It opens with the response a client sees and closes with a compiled, law-checked capstone, One 422, Every Bad Field. The old optics/record_mapping.html URL redirects. Two hands-on journeys sit beside it: Batching & Coupled Updates and Boundary Mapping.
  • Stock codec vocabulary: StandardCodecs ships one ValidatedPrism per standard conversion family (uuid, uri, localDate, instant, offsetDateTime, enumByName, bigDecimal, the numeric-from-string trio, booleanStrict, currency, locale), so a typical DTO boundary maps with no hand-written leaves. Every codec accepts exactly the canonical form it renders, and every failure is a located FieldError that feeds the 422 leg unchanged. See Standard codecs.
  • ValidatedPrism.canonical(message, parse, render) (plus a FieldError overload) wraps a throwing parser and a total render with the build-parse section law guarded per value: an accepted source must render back to itself, so a custom-canon codec is lawful without hand-writing the check. See Laws.
  • Records of any width: parse, the validated patch and fallible @GenerateMerge legs assemble records beyond 16 components through chunked Validated.fields() ladders, with the same located labels and declaration-order accumulation, law-checked at width. The only remaining bound is the JVM's constructor-slot limit on the record itself; the hand-written fields() ladder keeps its 16-field arity. See Diagnostics and limits.
  • One leaf vocabulary across tiers: the sparse UpdateSpec tier lifts element leaves over present List, Optional and Map properties exactly as the dense tiers do, every failing element located (phones.1), so one mix-in vocabulary serves a spec and its PATCH sibling. See Sparse PATCH write-back.
  • Generic mix-ins: a spec may extend a mix-in declaring type parameters, so extends Emails<EmailAddress> contributes ValidatedPrism<String, EmailAddress>, read under the spec's instantiation. A generic ancestor reached through a raw extends clause is still refused, naming the interface whose clause is raw. See Generic Specs.

Behaviour changes: a present same-typed identity container in a sparse PATCH now carries the dense tiers' null scan (tags.1: must not be null rather than a null reaching the domain); an Optional-typed PATCH property with a differing element type, previously rejected, patches through its element leaf; a spec extending a generic mix-in, previously refused, generates.

Generated optics for generic types (#721, #723, #730, #733, #736, #740, #742, #746, #750)

Every processor now reads a member or type under the instantiation the spec names rather than the source's own declaration, so a generic spec generates the signature it means, whatever it calls its parameters. Axis tests hold each generating annotation to the consuming build's -Xlint:unchecked,rawtypes -Werror.

  • @ImportOptics declares the type parameters its signatures name, in the spec's declaration order with bounds carried along: BoxOpticsSpec<U> extends OpticsSpec<Box<U>> generates <U>, and a concrete instantiation declares none. An optic method declaring parameters of its own is refused. @GeneratePrisms on a generic sealed hierarchy emits parameterised prisms. See Generic spec interfaces.
  • @InstanceOf narrows to what its test can check: the generated instanceof is written under the type arguments the source type pins (Circle<U> from Shape<U>), an unbounded wildcard elsewhere, and an array target through its component, and the generated methods no longer need @SuppressWarnings. A focus asking for more is refused with both remedies, widening to the wildcard or narrowing through @MatchWhen. See Parameterised targets.
  • @GenerateIsos answers a generic declaration at the declaration: an iso naming a type variable, an instance method, one taking arguments, one the generated package cannot see, or a return type that is not a two-argument Iso is each refused where it is written, with the remedy, rather than reported by javac inside the generated file. See Compiler Errors.
  • @ViaCopyAndSet(copyConstructor = ...) selects the constructor it names: the attribute resolves to the supertype of S it stands for and is emitted as a cast on the argument, new Config((BaseConfig) source), which is what picks between overloaded copy constructors. A name that does not resolve, is not a supertype, cannot be seen, or no constructor accepts is refused at the declaration. See Copy Strategies.
  • Type-use annotations reach generated source: a generated signature now carries the annotations the author wrote at every depth, so @Nullable String note stays @Nullable in the generated lens, which matters inside a consumer's @NullMarked package where a bare type means non-null. One exception by design: a focus widened through .nullable() drops the nullness the widening consumed, so @Nullable String label is AffinePath<Box, String> while List<@Nullable String> tags is TraversalPath<Box, @Nullable String>.

Behaviour changes, source-breaking in narrow places: a generated @ImportOptics method's arity can fall (OpticsSpec<Pair<A, String>> now generates <A>, not <A, B>), so a call supplying explicit type witnesses stops compiling; drop them, the inferred result is unchanged. An @InstanceOf focus naming a type argument the source does not pin (Prism<Shape, Circle<T>> on OpticsSpec<Shape>), and a target that is a parameterised member of a generic type, are refused. A copyConstructor name that resolves now emits a cast, which can select a different constructor than ran in 0.4.9; check overloaded types with LensLaws. Generated signatures that now carry @Nullable can change what a nullness checker reports in the consuming build.

The Focus DSL gives one answer (#711, #718, #719, #725, #756)

  • One widening analysis: ShapesFocus.tags() and OuterFocus.shapes().tags() come from one declaration and now report one path type. A navigation method composes the static Focus method for the field it reaches, so the two cannot drift, and a navigated path carries its field-name segments for pathString(). See Path widening.
  • Set and Collection components widen through the Each that rebuilds them: a Set through EachInstances.setEach(), a Collection through the new EachInstances.collectionEach(), and @GenerateTraversals gains a Collection generator. Every route to either, whether Focus, @GenerateTraversals, @ImportOptics or @ThroughField, bottoms out in one rebuild policy in Traversals: source iteration order preserved, nulls carried through, an unmodifiable set for a set source and an unmodifiable list for anything else. See Supported container types and Collection Components.
  • @Nullable detection reaches every recognised annotation: JSpecify's (TYPE_USE), JetBrains', AndroidX's and SpotBugs' @Nullable now widen to an AffinePath alongside JSR-305's and Jakarta's, and a container decides its own widening before @Nullable is consulted. See Focus DSL Reference.
  • A container the widening cannot be written for is explained at the declaration: an SPI container with a raw or wildcard type argument (Either<String, ? extends Leaf>, Set<?>) has no ground instantiation to infer its optic instance from, so the diagnostic names the component and the concrete alternative, and the generated file still compiles, leaving one message rather than two. See Compiler Errors.
  • @GenerateTraversals says when it passes over a container: a component that is a Collection or Map by erasure and reaches no generator (a Deque, a SortedMap, a raw List) draws a what/why/fix note where it is declared. A note rather than a warning, because the annotation has no per-component opt-out and a processor warning cannot be suppressed under -Werror. See Compiler Errors.

Behaviour changes, each replacing a path that threw ClassCastException on first use: a raw or wildcard Set or Collection component under @GenerateFocus is rejected; name the argument, or keep @GenerateLenses and @GenerateTraversals alone. Three navigator return types move to what the static method reports: a Collection subtype such as ArrayList is a FocusPath over the container; an SPI container of non-navigable elements stops at the container until the declaring record sets widenCollections = true; a nested List<Optional<String>> composes to the leaf, so drop the trailing .some(). A component carrying one of the four newly recognised @Nullables generates AffinePath where it generated FocusPath; read it with getOptional. Traversals.forSet() and traverseSet hand back an unmodifiable set (previously a mutable LinkedHashSet), and EachInstances.setEach() keeps source order across JVM runs.

Traversals for every component shape (#721)

  • @GenerateTraversals reads a wildcard type argument: List<? extends Leaf> focuses Leaf, and ? or ? super T focuses Object, for List, Map and every HKJ and third-party container alike. Four more shapes now generate: an array of a type that cannot be created by name (List<Leaf>[], Leaf[][]), a primitive array (boxed through, unboxed back), a generic record (record Holder<T>(List<T> items) gets <T> on the method), and a record naming its own F. Every container shape is compiled through the real processor by a sweep that also pins the element type each one focuses. See Wildcard Element Types and Generic Records.
  • Map traversals are linear: Traversals.traverseMapValues builds the result once rather than copying the map per entry (a 16 000-entry map traverses in 1.3 ms, down from 851 ms), and accepts a null key or a null the function hands back.

Spec interfaces explain what they cannot generate (#712, #723, #755)

  • A generated prism's focus is a variant of its source: @InstanceOf and @MatchWhen are both held to the rule at the declaration, since a prism built back with identity needs a focus that is itself a source. The book's examples now focus a variant, and Prism.of is the route where the value type is the point. See Spec Interfaces.
  • A default method on a spec interface is explained: a method body cannot be read during annotation processing, so the diagnostic names the two alternatives, a static method on the interface or a utility class calling the generated statics, rather than generating a stub. See Compiler Errors.
  • One error per problem: a rejected @InstanceOf target, an undetectable @ThroughField container or an unresolvable copyConstructor reports once, without a second "requires a hint annotation" message.

@GeneratePathBridge emits a bridge the consuming build compiles (#746, #748)

Members are read under the annotated interface's own instantiation and deduplicated across superinterfaces, with throws carried, varargs kept, a self-referential bound still inferring, and the bridge class declaring the interface's type parameters, so a generic service interface bridges cleanly under -Werror. IO<T> methods bridge through Path.ioPath, an inherited @PathVia is picked up, and the six shapes with no correct rendering are explained at the declaration. See Compiler Errors.

Behaviour changes: a varargs delegate's bridge method is varargs, so a bare null argument now needs (String[]) null; a raw effect return type, a wildcard Validated error type, and a static or private @PathVia method are refused; an interface with no @PathVia method draws a processor warning.

Effect handlers (#697, #699)

  • @ComposeEffects generates typed composition support: injectX() returns Inject<XKind.Witness, …>, functor(...) takes one Functor per effect and returns the composed EitherFFunctor, and BoundSet's components are each algebra's Bound, with no casts and no suppression. See Composing effects.
  • @EffectAlgebra accepts any name for the result type parameter, so RenamedOp<T> generates as Op<A> does, and the three permit shapes it cannot generate for (dropping, pinning or bounding the algebra's parameter) are explained at the declaration. See Defining effects.

Behaviour changes / migration: the last effect in a composition is now injected at the depth its arity implies, which corrects the dispatch of programs that reach it through the generated *Support. Each @ComposeEffects field must be Class<XOp<?>> naming an @EffectAlgebra; BoundSet<F> becomes BoundSet; functor() and BoundSet's accessors change erasure. A *Support is generated in your own build, so recompiling is the whole migration.

Compile-time checks

  • HKJ compiles under its own checker: every module builds with -Xplugin:HKJChecker at zero findings. discarded-effect now gates on a sealed Deferred capability, so Path.io(() -> 1).peek(log) (a silent no-op) is reported while Path.just(1).peek(log) (already run) is not, and a requireNonNull guard that hands the effect back is read as the pass-through it is. The checker skips @Generated types, and a single declaration opts out of one check with @SuppressWarnings("<check-id>"), or of all of them with @SuppressWarnings("hkj-checker"). See Compile-Time Checks.

Documentation

  • The house style: the mapping chapter established it (the payoff shown before the theory, the common case served before the fine print, mermaid for flows and decisions in a theme-safe palette, "Why this matters" beside the rule it justifies, Key Takeaways on every page), and it now runs through the Resilience chapter and every sub-chapter of Optics, the optics diagrams included. The style guide records the conventions.

v0.4.9 (31 July 2026)

This release completes the @GenerateMapping mapper programme: every wire shape a REST boundary throws at a record domain now maps through one spec convention, under one null doctrine, rendering as one 422 response.

The full mapper (#628, #645, #625, #624, #623)

  • Bean-shaped wires: a mutable getter/setter class or a builder-constructed immutable one (JAXB getter-only collections included) maps like a record, with the full feature set (renames, leaves, derived fields, container lifting, nesting both ways) and a domain Optional<T> bridging to a nullable property. asIso() stays truthful: an all-primitive bean earns it, a reference-reading one does not. See Bean-shaped wire targets.
  • Sparse PATCH write-back: a spec extending UpdateSpec<D, W> (bean wire only) generates updateFrom(Wire) : Edits.Accumulated<Domain>, the REST PATCH contract as a validated fold: null means keep, present values are set or parsed, every invalid present field reports at once, and shapes that cannot honour null-as-absent (record wires, primitives, Optional bridges, sealed hierarchies) are rejected. The example app gains a worked PATCH /api/users/{id} endpoint (#647). See Sparse PATCH write-back.
  • The validated patch tier: a projection carrying fallible correspondences swaps asLens() for a dense patch(domain, wire) : Validated<NonEmptyList<FieldError>, Domain> write-back, validating every projected component at once while unprojected ones survive by construction. Deliberately the opposite of the sparse tier: patch treats a missing value as an error, updateFrom as keep-the-current-one. See Leaf-carrying projections.
  • Generic records, three ways, all nestable: concrete instantiations (MappingSpec<Page<User>, PageDto<UserDto>>), threaded specs (one generic Impl behind instance()), and element-mapped specs (an abstract ValidatedPrism<TDto, T> leaf supplied through a generated of(...) factory), with use sites resolving by type-argument unification and composing of(...) in place. Record-to-record only; raw and wildcard uses are diagnosed. See Generic records.
  • Shared vocabulary: specs may extend plain mix-in interfaces carrying renames, leaves and derived fields, collected with Java's own precedence and diagnosed with the declaring interface named. See Shared vocabulary.
  • Every tier is law-checked through MappingLaws (including located-validation clauses on both write-backs) and pinned by per-tier golden Impls, so generator drift fails the build.
  • Build tooling: every annotation processor registers with Gradle's incremental annotation processing (nineteen in hkj-processor, plus the Spring client processor; #677), so consuming source sets keep incremental compilation. Lombok interop is covered by test: a @Data class works as a bean-shaped wire, provided Lombok is listed before hkj-processor on the processor path (the reverse order is diagnosed). See Manual setup.
  • Injection and testing documented (#678): register the surface you consume as a bean (ValidatedPrism for parse-capable mappings; method references for build, patch and updateFrom), and fake it as a value with ValidatedPrism.of(...) (the interface is sealed, so mocking is impossible by design). The Spring example app demonstrates the seam end to end. See Injecting and testing generated mappings.

One null doctrine (#653, #659, #660)

A JSON binder leaves a missing property null on a record component just as on an unset bean property, so every reference-typed read on every accumulating surface (parse on both wire shapes, patch, fallible @GenerateMerge legs, ValidatedPrism.parseAll/parseValues) is guarded into a located, accumulating FieldError (must not be null), never an exception; failures locate through nesting and inside containers by index or key (customer.name, emails.1), identity-copied containers included. The caller-contract boundaries: a null wire, source argument or map key stays requireNonNull, and the total directions (build, asLens().set, asIso().reverseGet) stay unguarded by declaration. A lossless record mapping keeps asIso(), its guards covering hostile bindings only.

Behaviour changes against v0.4.8, all in the friendly direction but observable:

  • parse and fallible assemble on null-carrying wires previously threw NullPointerException; they now return located Invalid (a 422 instead of a 500 at a Spring boundary). This includes null elements inside containers, which previously threw from the bulk forms or passed through identity copies unexamined.
  • List-element failures gain their index segment: emails: not an email address becomes emails.1: not an email address in 422 payloads (bracketed rendering stays deferred to the future path-segment model).
  • The generator is stricter about silent mistakes: a locally declared leaf naming no domain component is now a compile error with a nearest-name hint (Did you mean 'email()'?) instead of silently validating nothing; spec methods colliding with generated members are rejected (#654); and a leaf on a projected component now takes effect (selecting the patch tier) instead of being silently ignored.

Spring: the 422 leg (#627)

A controller returning any all-located-FieldError Invalid (a mapper parse, patch or updateFrom result, a Path.fields() assembly, an @GenerateAssembly result) renders as a single 422 Unprocessable Content response listing every bad field, each with its display path, lossless segments and message. The leg is chosen by payload shape alone; the status is configurable via hkj.web.validation-field-error-status. Paths use domain component names, renames included. See The 422 leg.

Behaviour change: a pre-existing endpoint returning an all-FieldError payload moves from 400 to 422 on upgrade; set hkj.web.validation-field-error-status: 400 to restore it. Tests and examples adopt Spring Framework 7's HttpStatus.UNPROCESSABLE_CONTENT (the RFC 9110 rename).

hkj-spring hardening (#642)

A review pass fixed a security fail-open in the JWT converter, async-handler lifecycle defects, a Jackson contextual-binding bug, and dead configuration.

Breaking changes / migration:

  • SSE responses commit after the first stream element resolves, so a stream failing at its start returns the configured hkj.web.vstream-failure-status instead of a broken 200; EventSource.onopen fires on the first element, so long-idle streams should emit an early heartbeat.
  • Removed dead @ConfigurationProperties surface (source-breaking for programmatic-config consumers): HkjProperties.Jackson.SerializationFormat and the *-format accessors, HkjProperties.Validation (hkj.validation.*), the hkj.async.executor-* fields, EffectConfig.startupValidation/interpreterSelection, and EitherAuthorizationManager.AuthorizationSuccess. Delete the keys; none altered behaviour.
  • hkj.security.validated-user-details defaults to false and starts empty (sample accounts moved to ValidatedUserDetailsService.withSampleUsers()); enabling it without registering accounts fails every login.
  • A JWT with a missing or malformed authorities claim is rejected (401) instead of authenticating with empty authorities; set hkj.security.reject-missing-authorities-claim: false for lenient missing-claim handling.
  • Effect-boundary interpreter resolution fails fast at startup on ambiguous or profile-gated-away matches, previously silent scan-order selections.

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 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 deserializers resolve generic element types: The Either/Validated/NonEmptyList deserializers 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

v0.4.7 (26 June 2026)

@HkjHttpClient: generated Effect-Path HTTP clients

hkj-spring was server-side only: the *PathReturnValueHandlers map an EitherPath/VTaskPath/… returned from a controller into an HTTP response, but when service A called service B, B's typed error collapsed into a raw status code at A's boundary, losing the typed error channel the library is built around. The new @HkjHttpClient closes that gap with the client-side inverse, preserving the typed error end-to-end across services. Two additive modules (hkj-spring/client runtime + hkj-spring/client-processor), no breaking changes.

  • Runtime (hkj-spring/client): HkjClientExchange folds an HTTP exchange into an Effect Path: either (2xx→Right, 4xx/5xx→Left), eitherVTask (deferred on a virtual thread, so callers get withRetry/withCircuitBreaker/timeout), and maybe (404/empty→Nothing). A pluggable ResponseErrorDecoder decodes the server's {"success":false,"error":…} envelope into the declared error type via the shared Jackson mapper; auto-configuration contributes the default factory.
  • Codegen (hkj-spring/client-processor): annotating a Path-typed @HttpExchange interface generates a native @HttpExchange interface (return types unwrapped to ResponseEntity<T>, all mapping/parameter annotations copied through), a …Client implementation that dispatches by return type, and a …ClientConfiguration that wires the client via Spring 7 @ImportHttpServices; base URL/timeouts/versioning come from spring.http.serviceclient.<group>.*.
  • A concrete error type decodes with no extra annotations; a sealed DomainError hierarchy needs @JsonTypeInfo/@JsonSubTypes. The processor is wired into hkj-spring-boot-starter; the hkj-spring/client-example module is a standalone client application that calls the server example over HTTP (with an end-to-end MockRestServiceServer test), and the Declarative HTTP Clients guide walks through it.
  • Additional capabilities: @OnStatus(value, error) maps individual statuses to distinct error subtypes (404 → UserNotFoundError, …); generic @HkjHttpClient interfaces are supported codegen-only; ClientErrorResponse.retryAfter() exposes the server's Retry-After hint for back-off; and HkjClientExchange.vstream(...) consumes the server's SSE stream into a VStreamPath<T> (deferred, resource-safe). The runtime itself is written in the library's own idioms (Try/Either), and the client is documented across the hkj-spring module docs plus a dedicated HTTP_CLIENT.md.

Spring Boot 4.1.0 / Framework 7.0.8 upgrade

The hkj-spring modules move from Spring Boot 4.0.6 to 4.1.0 (Spring Framework 7.0.8), with the managed Jackson 3.x line advancing from 3.1.2 to 3.1.4 to match the jackson-bom shipped by Boot 4.1.0. Dependency-only, centralised in the version catalog; the modules compile and pass against 4.1.0 with no source changes and no new deprecations. No public API change (#575).

Writer.of(log, value) factory

New Writer.of(W log, @Nullable A value) static factory for the common custom-log-plus-value case, sitting between Writer.value(Monoid<W>, A) (empty log) and Writer.tell(W) (Unit value). The (log, value) order mirrors the record components and accessors, and returning the plain Writer<W, A> launders the @Nullable A nullness contract that the raw constructor's diamond leaks at the call site. Purely additive (#554).

Consistent recoverWith / recover null-handling across MonadError

recoverWith(ma, fallback) now rejects a null ma/fallback eagerly and identically on every MonadError instance (TryMonad, OptionalMonad, VTask, CompletableFuture, EitherT/MaybeT/OptionalT), replacing the previous state-dependent, mislabelled NullPointerException (EitherMonad/ValidatedMonad already guarded it). recover(ma, value) keeps its @Nullable value, so recover(failure, null) stays a valid Success(null)/Nothing/empty; ValidatedMonad keeps only recoverWith because its of rejects null. Behaviour-preserving except on null input (#553).

Internal: type-safety and soundness cleanups

A sweep across hkj-core removing avoidable unchecked casts and holder indirection; behaviour-preserving with no public API change unless noted:

  • Turned on -Xlint:unchecked,rawtypes -Werror across all modules, so any new unchecked or raw-type use must carry an explicit suppression; generated @ComposeEffects Support classes carry one so downstream lint-enabled builds stay clean (#560).
  • Maybe/Either/Validated roots now extend their Kind interfaces, making the five widen/widen2 methods cast-free upcasts (#561).
  • Every remaining HKJ-owned type direct-implements its Kind: widen is an allocation-free upcast, seventeen *Holder records are deleted, and narrow(null) now uniformly raises KindUnwrapException (Lazy and the JDK-wrapped types keep their holders) (#568).
  • Funnelled the covariant flatMap/recoverWith reinterpretations through a private covary helper, and replaced the public API's last raw-Kind wrapper (IndexedTraversal.asIndexedFold()) with a typed IdBox (#562).
  • Consolidated Free.foldMap's two stack-safe interpreters behind the single Natural path (#563).

EachIndexed: type-safe replacement for Each.eachWithIndex()

Each.eachWithIndex() returned Optional<IndexedTraversal<I, S, A>> with a caller-chosen index type, so requesting the wrong index compiled and then failed at runtime with a ClassCastException. New EachIndexed<I, S, A> extends Each<S, A> carries the real index type at the type level and exposes indexedTraversal() directly (no Optional, no cast); the EachInstances factories now return it. Each.eachWithIndex() is deprecated for removal in 0.5.0 and still works as a bridge, so existing code compiles. Additive plus one deprecation, no behaviour change for existing callers (#564). See Each type class / Indexed Optics.

raw-kind checker rule

The HKJ compiler plugin gains a raw-kind rule: a raw Kind/Kind2 drops its witness type argument (the one route that lets a value tagged with one witness be narrowed through another, compiling silently and throwing KindUnwrapException at runtime), and javac accepts it, so the checker is the sole compile-time signal. Flags variable/parameter/field declarations and casts at warn by default (disable=raw-kind, severity:raw-kind=error); a properly parameterised Kind<W, A> is never flagged (#565). Documented in Compile-Time Checks.


v0.4.6 (7 June 2026)

Optic-Driven Request Batching

This release adds the user-facing surface around the org.higherkindedj.optics.fetch substrate so an optic traversal of N foci collapses to one batched backend call by swapping the strategy passed to Optic.modifyF. The optic core is untouched; the package is now exported and documented, the heterogeneous Id -> Entity case has a dedicated helper, and run-time failures land on the value channel as Either rather than as exceptions.

  • Optic-Driven Batching: New chapter under Optics / Integration and Recipes, with three diagrams (N+1 vs batched, the optic + applicative + runner pipeline, the applicative-versus-flatMap round cost) and a language-agnostic primer link for the DataLoader/Haxl idea. org.higherkindedj.optics.fetch is now exported from the core module; Done/Blocked/PendingKeys remain package-private so consumers interact with Fetch only through its static factories and runners
  • SafeFetch: Total runner that captures resolver exceptions, missing-key reports, loader failures, and deadlines as Either.left values on the value channel instead of thrown exceptions; SafeFetch.runCached, runAsync, and runAsyncWithTimeout never throw and the safe-async future never completes exceptionally. SafeFetch.partition splits a per-key Either<E, V> result list into aligned successes and failures so a partial-success batch is preserved end-to-end
  • SourceRouter.routed: Composes per-source BatchLoaders with a classifier into one loader the substrate can call; one round fans out to one concurrent dispatch per source, so a list mixing user ids and product skus produces exactly one call per backend, not one call per key
  • BatchLoaders.chunked(loader, maxSize): Caps a single dispatch's size for backends that enforce a per-request limit ($in clause cap, HTTP query-string ceiling, GraphQL batch limit); the substrate still sees one round and the loader splits the keyset behind the curtain
  • FetchOptics.fetchEach(source, rebuild): The type-changing list-traversal the codegen does not produce (codegen optics are type-preserving, so a Traversal<Team, UserId> cannot directly describe loading each UserId into a User); builds an Optic<S, T, A, B> from a list-reader and a rebuild function so heterogeneous fetch composes with the rest of the optic graph
  • Tutorial 21: New tutorial journey (exercise + teaching-solution) covering the four pieces (same-type batching, heterogeneous fetch, multi-source routing, railway errors) with five exercises and tiered hints; the solution carries the Why this is idiomatic / Alternative / Common wrong attempt commentary per exercise
  • Architecture-rule update: The optics.fetch package is exempted from the "no specific HKT type dependencies" rule alongside the existing optics.util, optics.extensions, and optics.fluent exemptions, on the same basis: SafeFetch's railway runner is built around Either by design

Plan Introspection and Guardrails for Optic Batching

The audit and safety-rail layer on top of Optic-Driven Batching: a way to fold a Fetch program into a structural plan without I/O, and a per-round guard that interposes between the program and its resolver to refuse runaway batches before they leave the JVM. Both compose with SafeFetch so refusal is a value, not a thrown exception.

  • Plan Introspection and Guardrails: New chapter under Optics / Integration and Recipes covering the offline Plans.preflight walk, the per-round Guard family, and the railway-safe refusal pattern.
  • Plans.preflight: Folds a Fetch program into a Plan<K> with zero I/O. Each round's keyset is recorded in dispatch order; round 1 is universally observable, and later rounds are walked on stub values when the program's combine logic tolerates null (a Plan.truncated() flag is the honest signal otherwise).
  • Guards.maxKeysPerRound / maxRounds / maxBackendCalls / audit / none: Standard guards that pass or refuse a round at the runner boundary; compose with Guard::and. A refusal aborts the run with GuardViolationException carrying the offending roundIndex and pendingKeys.
  • Guards.runCached / runAsync: Drop-in replacements for the substrate runners with the guard interposed; the resolver is never called for a refused round.
  • SafeFetch.runCachedWithGuard / runAsyncWithGuard: Railway variants that capture a refusal as Either.left(GuardViolationException); the run never throws and the safe-async future never completes exceptionally.
  • Tutorial 22 (exercise + teaching solution) covering the five pieces (preflight, truncation, refusal, audit, railway capture).

Test-suite consolidation (internal)

Mostly an internal refactor of the hkj-core test suite The one user-visible piece is in hkj-test: new reusable law helpers (FunctorLaws, ApplicativeLaws, MonadLaws, SelectiveLaws) and a KindEquivalence.byEqualsAfter helper that downstream users can call to verify their own type-class instances. The Kind-accepting overloads on EitherAssert / MaybeAssert / TryAssert / IOAssert / LazyAssert / ReaderAssert / ValidatedAssert / WriterAssert / VStreamAssert / VTaskAssert now match the auto-narrowing pattern of ListAssert / OptionalKindAssert / StreamAssert / IdAssert.

N-ary Coupled Lenses

The arity ladder above Lens.paired: a record with three or more cross-field invariants can now be updated atomically through a single coupled3..coupled9 call instead of nested paired workarounds.

  • Coupled Fields chapter: "Three or More Coupled Fields" section rewritten to show the new ladder; the old "nest pairs / feature request" guidance is replaced.
  • CoupledLenses.coupled3 ... coupled9 (in org.higherkindedj.optics.util): seven static factories, each with two overloads mirroring Lens.paired exactly (preserving form taking (S, A, B, ...) -> S; simple form taking the constructor reference (A, B, ...) -> S). Returns Lens<S, TupleN<...>> reconstructed atomically.
  • hkj-processor adds CoupledLensGenerator, wired into the existing @GenerateForComprehensions trigger alongside the Tuple/For-step generators. Generation caps at arity 9 (cross-field invariants past that point are vanishing in practice); raising the cap is a one-line change to the generator.
  • Tutorial 23 (exercise + teaching solution) demonstrating coupled3 on a 3-field monotonic invariant and coupled5 on the same shape at higher arity, including the canonical "chained set throws" failure mode that coupled lenses sidestep.

API Deprecations Ahead of 0.5.0

This release prepares users for a record-shape change to StateT that lands in 0.5.0. The single-argument runner methods and the explicit monadF() accessor are deprecated now so call sites can migrate ahead of time; the record component itself stays in place until 0.5.0.

  • StateT runner methods accept an explicit Monad<F>: new overloads StateT.evalStateT(state, monad) and StateT.execStateT(state, monad) use the supplied monad rather than the one stored on the record. The matching helpers StateTKindHelper.evalStateT(kind, state, monad) and execStateT(kind, state, monad) are added on the same shape. New code should prefer these overloads; the single-argument forms are deprecated for removal in 0.5.0 (#445)
  • StateT.monadF() deprecation: the explicit accessor is now @Deprecated(forRemoval = true). In 0.5.0 the monadF record component itself is removed so that two StateT values with the same state function are considered equal regardless of which Monad instance they were constructed with. equals, hashCode, and toString therefore change in 0.5.0; until then the record-generated implementations still discriminate on the stored monad, which is the underlying defect the deprecation is staging
  • Internal call sites in MutableContext.evalWith and execWith are migrated to the new two-argument overloads; library builds emit no deprecation warnings of their own

Try.fold and TryPath.fold argument-order rename ahead of 0.5.0

Try.fold(successMapper, failureMapper) and TryPath.fold(successMapper, failureMapper) are the two surfaces whose fold is success-first, against the error-first convention used by Either.fold, Validated.fold, EitherF.fold, EitherPath.fold, and ValidationPath.fold. A naked argument swap on fold would silently invert behaviour for the common case of parameter-ignoring lambdas (v -> v, v -> null, Throwable::getMessage) and for Object-typed method references, because both Function parameters have different generic bounds but lambdas often resolve to either. The fix is published under a distinct name so the change becomes a compile error rather than a runtime inversion.

  • Try.foldFailureFirst(failureMapper, successMapper) and TryPath.foldFailureFirst(failureMapper, successMapper) are the canonical error-first replacements, added since 0.4.6. Their argument order matches Either.fold / Validated.fold / EitherF.fold / EitherPath.fold / ValidationPath.fold. The methods are named foldFailureFirst rather than overloading match (which would create a lambda-inference ambiguity with the existing Try.match(Consumer, Consumer)) or fold (which would silently invert behaviour for downstream call sites).
  • Try.fold(successMapper, failureMapper) and TryPath.fold(successMapper, failureMapper) are now @Deprecated(forRemoval = true) and are removed in 0.5.0. Internal call sites across hkj-core (TryApplicative, TryTraverse, TryPath, PathOps, VTaskContext, LensExtensions, Affines), hkj-test (VTaskPathAssert, VTaskContextAssert), hkj-spring return-value handlers, hkj-processor-plugins (the TryGenerator annotation-processor template), and every runnable example, tutorial, and solution are migrated to foldFailureFirst; library builds emit no deprecation warnings of their own.
  • The canonical name fold is planned to be reintroduced on both Try and TryPath with the error-first argument order in 0.6.0, once 0.5.0 has removed the success-first fold and every reachable call site has been forced through the renamed method. See #452 for the design rationale and the follow-up issue tracking the 0.6.0 reintroduction.
  • OpenRewrite migration: a new SwapTryFoldToFoldFailureFirstRecipe is added to the existing MigrateDeprecationsTo0_5_0 recipe group in hkj-openrewrite. The recipe matches Try.fold(successMapper, failureMapper) and TryPath.fold(successMapper, failureMapper) call sites and rewrites them to foldFailureFirst(failureMapper, successMapper), atomically renaming the method and swapping the two arguments. It cannot be expressed as a stock ChangeMethodName invocation because the argument order changes; a naked rename would silently invert behaviour. Downstream consumers run org.higherkindedj.openrewrite.MigrateDeprecationsTo0_5_0 to migrate Try.fold, TryPath.fold, StateTKind.narrowK, and KindValidator.narrowWithPattern in one pass.

v0.4.5 (22 May 2026)

A Uniform Instances Facade for Type-Class Lookup

This release introduces Instances, a single static entry point for obtaining any built-in type-class instance, replacing the three inconsistent legacy idioms (a static INSTANCE field, a generic instance() method, or an argument-taking constructor) with one predictable shape discovered by capability through IDE autocomplete. The whole codebase (tests, runnable examples, and the book) is migrated to the one idiom.

  • Obtaining Instances: New org.higherkindedj.hkt.instances package with the Instances facade and the Witnesses typed-token helper. Instances.monad/applicative/functor(token) are total (every canonical instance is at least a Monad); a single token yields all three by Java subtyping. Phantom-typed witnesses (Either, Reader, Context, State) still infer their type parameter from the assignment target, matching EitherMonad.<L>instance() behaviour. The facade is a thin static re-export of the existing accessors (not Spring-wired, not PathRegistry/ServiceLoader-backed) so compile-time safety is preserved and no built-in instance can be missing at runtime (#522)
  • Partial capability lookups: Instances.monadError, monadZero and alternative for canonical instances that implement the richer capability (e.g. Maybe, Optional, Try, Either, List, Stream). The error type E of monadError is inferred from the assignment target; asking for a capability the instance does not have fails fast with a ClassCastException, exactly as calling a non-existent method would
  • Argument-carrying re-exports: Instances.validated(Semigroup), writer(Monoid), eitherT(outer), maybeT(outer), optionalT(outer), readerT(outer), stateT(outer) and writerT(outer, Monoid). The structurally-required dependency is now a compiler-enforced, self-documenting method parameter instead of something discovered by reading a constructor
  • One-idiom migration: The Instances facade is adopted across ~196 test files, 66 runnable examples, and 71 book pages so the documentation and examples teach a single way to obtain an instance. The MonadReader/MonadState MTL capability classes and Traverse/Selective/Foldable remain a separate surface, intentionally out of scope for this facade and tracked separately
  • Reference material: New glossary entry, a Type-Class Instances section in the cheat sheet, and a InstancesFacadeExample runnable example
  • Bifunctor law verification: The reusable Bifunctor law harness (LawTestPattern/TypeClassTestPattern) now verifies the first-map (first(f, fab) == bimap(f, id, fab)) and second-map (second(g, fab) == bimap(id, g, fab)) consistency laws alongside the existing identity and composition laws, so every canonical instance (Either, Tuple, Const, Validated, Writer) is checked against all four laws; explicit named consistency tests added for Either (Left/Right) and Tuple (#461)
  • Collection-path fold family: ListPath and StreamPath gain the monoid-style fold(identity, op) and the Foldable-style foldMap(Monoid, fn), plus foldRight on StreamPath, matching the existing VStreamPath/VStreamContext fold surface so the same reduction reads identically across every sequence-like path and stays inside the path chain. Documented in the cheat sheet and the Foldable chapter, with CollectionPathsExample updated to fold without unwrapping (#462)
  • Effect Path toString() standardisation: A shared PathToString helper gives every Effect Path type one debugging-friendly, greppable toString() convention: the round-parenthesis wrapper form TypeName(inner), a uniform angle-bracketed sentinel vocabulary (<deferred>, <stream>, <empty>, <pending>, <failed>), and bounded rendering for collection-backed paths (ListPath, NonDetPath, WriterPath logs) with an explicit …(+k more) marker so a large backing collection never produces an unbounded log line. IdPath is now null-safe and LazyPath never forces its computation when rendered; all changed path classes hold at 100% line/branch coverage (#530)

Expanded hkj-checker Compile-Time Diagnostics

This release grows the hkj-checker javac plugin from a single Path-type-mismatch check into a catalogue of twelve compile-time checks, adds per-check severity configuration, and consolidates the relevant OpenRewrite recipes into compile-time feedback. Several further candidate checks were investigated and deliberately not shipped (kept as passing characterization tests that document why) because the targeted error is unreachable on modern javac, already caught by the compiler, or only detectable via a rot-prone heuristic; the strict no-false-positives policy is preserved throughout.

  • Compile-Time Checks: Eleven new checks join path-type-mismatch: effect-composition, transformer-missing-monad, free-switch-exhaustive, discarded-effect, state-t-mapt-arity, error-type-mismatch, kind-value-narrow, witness-arity, via-non-path, map-nests-effect, and migration-nudge. Each is a companion to a real javac error or the sole signal for an otherwise-silent mistake (a discarded lazy effect, a silently-erased error type, a nested effect); the sole-signal heuristics default to a warning
  • Per-check severity: The plugin-argument grammar adds severity:<id>=error|warn alongside the global severity= and disable=<id>, so the warn-default checks can be promoted per project. Unknown ids and unparseable values are ignored so a typo never breaks the build
  • Recipe consolidation: migration-nudge folds the ConvertRawFreeToFreePath and DetectInjectBoilerplate OpenRewrite diagnoses into advisory compile-time nudges; free-switch-exhaustive and witness-arity do the same for the Free-switch and WitnessArity recipes
  • Documentation: tooling/compile_checks.md is now the authoritative checker catalogue (every check, its default severity, and the configuration grammar); the effect/transformers/optics Common Compiler Errors chapters were corrected where they described errors modern javac no longer emits and cross-linked to the catalogue

Hardened hkj-openrewrite Recipes

This release audits and hardens the hkj-openrewrite migration recipes: correctness fixes, broader detection, type-safe matching, new 0.5.0 deprecation recipes, and a near-quadrupled test suite (9 → 34 tests).

  • Arity bounds: AddArityBoundsToTypeParameters now emits TypeArity.Binary for Kind2, Bifunctor and Profunctor (previously always Unary, which generated incorrect bounds), detects witness use across fields, local variables, the class hierarchy, nested generics and wildcard bounds (not just method signatures), and no longer emits malformed output (<Fextends …>); the existing-bound intersection case is also fixed
  • Type-safe detection: ConvertRawFreeToFreePath and DetectInjectBoilerplate use a type-attributed MethodMatcher instead of rendered-string matching (a user type named Free, fully-qualified calls, or static imports no longer mis-fire or get missed); AddHandleErrorCase now also handles switch expressions with whole-word case matching; the three detect-only recipes emit OpenRewrite search-result markers instead of rewriting source with TODO comments
  • 0.5.0 deprecation migration: New MigrateDeprecationsTo0_5_0 recipe group renames StateTKind.narrowKnarrow and KindValidator.narrowWithPatternnarrowHolder

Library and Build Refinements

This release also marks one wildcard-witness escape hatch for removal, makes the Java 25 toolchain self-provisioning, and lands the module-internal foundation for batched optic data access.

  • StateTKind.narrowK deprecation: StateTKind.narrowK accepts a wildcard-witness Kind<?, A>, bypassing the HKT witness type safety enforced everywhere else in the library; it has no callers and the type-safe narrow(Kind) already covers the use case. It is now @Deprecated(forRemoval = true) for removal in 0.5.0, with the MigrateDeprecationsTo0_5_0 OpenRewrite recipe automating the narrowKnarrow rename (#455)
  • Java 25 toolchain auto-provisioning: settings.gradle.kts applies the foojay-resolver-convention plugin so Gradle downloads and provisions a matching Java 25 JDK automatically when the build machine does not already have one, removing a manual setup step for new contributors
  • Request-batching substrate: New module-internal org.higherkindedj.optics.fetch package: a free-applicative-style Fetch<K, V, A> (Done/Blocked) and FetchApplicative that plug into the optic modifyF seam so a traversal whose focused values are loaded from a backend coalesces those N loads into one batched call (the classic N+1), with a transport- and datastore-neutral BatchLoader contract and round-based runCached/runAsync runners carrying a per-run request cache. The package is intentionally not exported; it is the foundation for later data-access capabilities and carries no public API yet (#539)

v0.4.4 (16 May 2026)

The hkj-test Module, PCollections Integration, and Type Class Enrichments

This release ships hkj-test, a new publishable module providing fluent AssertJ assertion helpers for every public Higher-Kinded-J type, validates and extends PCollections persistent-collection support across the HKT and optics infrastructure, enriches the type class hierarchy with Alternative.orElseAll(Iterable) and MonadZero.filter, makes ForState.zoom and ReaderPath.magnify optic-polymorphic, standardises the internal validation package, and refreshes the Tooling chapter to lead with the recommended build-plugin setup.

  • hkj-test Module: New publishable module (io.github.higher-kinded-j:hkj-test) with 19 user-facing assertion classes covering the discriminated unions (Either, Maybe, Try, Validated, Lazy), the Reader/Writer/State trio, the effect types (IO, VTask, VStream), every monad transformer, and the Free/EitherF algebras. Published as a JPMS module (org.higherkindedj.test) so a single dependency declaration suffices; Java 25 with --enable-preview can import module org.higherkindedj.test; to bring every helper into scope. Backed by an AssertContract<S, A> contract-test framework holding the module at a 100% line+instruction coverage gate, plus a new /hkj-test Claude Code skill
  • hkj-test Coverage Extension: Seven further assertion classes promoted from hkj-core test sources into the published artifact: the List/OptionalKind/Stream/Id Kind-narrowing wrappers (assertThatList, assertThatOptionalKind, assertThatStream, assertThatId) and the VTaskPath/VStreamPath/VTaskContext path-and-context assertions
  • PCollections HKT Compatibility: Validates that PCollections persistent collections (PVector, PStack) work through the existing ListKind/ListMonad/ListTraverse/ListSelective/Alternative infrastructure via java.util.List compatibility with no production code changes, backed by integration tests, jQwik property tests for the Functor/Monad/Foldable laws, JMH benchmarks, and a runnable example
  • PCollections Optics Generators: Seven TraversableGenerator plugins teaching @GenerateTraversals and @GenerateFocus to navigate PCollections types (PVector, PStack, PSet, PSortedSet, PBag, PMap values, PSortedMap values); auto-discovered when org.pcollections is on the annotation-processor classpath. The generator ecosystem grows from 23 to 30 implementations
  • Traversals.forMapValuesCollecting: Map-shaped companion to forIterableCollecting, with a bounded single-arg overload for java.util.Map subtypes (PCollections PMap/PSortedMap, Guava ImmutableMap) and an unbounded two-arg overload for non-java.util.Map types (Eclipse Collections, Vavr); EachInstances.mapValuesEachCollecting mirrors both for the Focus DSL
  • Alternative.orElseAll(Iterable): Dynamically-sized counterpart to the existing varargs orElseAll and analogue of Haskell's asum/msum, folding an iterable of alternatives via orElse. ListMonad and StreamMonad override it to avoid O(n^2) result copying and deeply-nested Stream.concat chains while preserving lazy evaluation
  • MonadZero.filter: New default filter(Predicate, Kind) derived from flatMap + of/zero, with allocation-free ListMonad/StreamMonad overrides; the duplicated guard pattern is refactored out of For.when, ForState.when/zoom, and the ForPath comprehension builders to call filter directly
  • Axes of Transformer Transformation: ForState.zoom now accepts FocusPath, AffinePath (short-circuiting via MonadZero.zero() when the focus is absent), and Iso in addition to Lens; ReaderPath gains optic-aware magnify(Getter) and magnify(FocusPath) overloads alongside the existing local(Function) escape hatch. New chapter plus a MagnifyServiceLayerExample and Tutorial 05 (Optic-Polymorphic Zoom and Magnify)
  • Validation package standardised: the Operation enum gains WIDEN/NARROW/OR_ELSE_ALL/FILTER, Validation exposes KIND/FUNCTION/TRANSFORMER/CORE static fields, FunctionValidator gains validateMap (44 Functor/Monad sites migrated), and KindValidator.narrowWithPattern is @Deprecated(forRemoval=true) for removal in 0.5.0 in favour of the new narrowHolder

Documentation & Tutorial Improvements

  • Build Plugins as the Documented Default: The Tooling chapter is reordered so Build Plugins leads as the recommended path and Manual Setup follows as the explicit fallback, with Previous/Next navigation rewired across the chapter to keep the sequence linear. The Spring Boot Quickstart gains an hkj-bom option (Gradle and Maven forms) so all HKJ module versions are declared once
  • Where to Start: New task-first landing page that asks "what are you trying to do?" before routing to the chapter-level decision trees, with five top-level branches (failure/absence, nested data, async/IO, sequencing, polymorphic code) plus a Combining Tools section covering the most common cross-axis combinations

v0.4.3 (7 May 2026)

Pluggable HTTP Error Status Strategy, Header Carriers, and Documentation Refresh

This release introduces pluggable error-to-status mapping for hkj-spring, lets domain errors inject custom HTTP headers (Retry-After, WWW-Authenticate, Location, ...), and delivers a comprehensive refresh of the hkj-book: the Effect Path API chapter restructured into five sub-chapters, optics documentation reorganised along Diátaxis lines, the Monad Transformers chapter rebuilt as a coherent learning path with a hands-on tutorial track, the Foundations chapter rewritten around a single recurring "one line, six layers" anchor, and refreshed hands-on materials with tiered hints and per-exercise teaching prose across every tutorial journey.

  • HttpHeaderCarrier: Mix-in interface for error values to inject custom HTTP headers into the response. All Effect Path return-value handlers now apply carrier headers before writing the JSON body, enabling 429 Too Many Requests errors to surface Retry-After, 401 Unauthorized errors to surface WWW-Authenticate, and 201 Created / 301 Moved Permanently outcomes to surface Location
  • ErrorStatusCodeStrategy: Pluggable strategy bean replacing the hard-coded heuristics in ErrorStatusCodeMapper. The default DefaultErrorStatusCodeStrategy combines explicit mappings from hkj.web.error-status-mappings (by simple or fully-qualified class name) with token-aware heuristics on the simple class name and the configured default status code; teams can supply a custom ErrorStatusCodeStrategy bean to override end-to-end
  • hkj.web.error-status-mappings: New configuration property for explicit error-class to HTTP-status mappings, supporting both simple and fully-qualified class names. Covers 4xx/5xx codes outside the heuristic table such as 409 Conflict, 422 Unprocessable Entity, 429 Too Many Requests, and 503 Service Unavailable
  • Tokenized class-name matching: ErrorStatusCodeMapper now splits class names on CamelCase boundaries and matches whole tokens, eliminating false positives like RevalidationError previously matching the validation heuristic

Documentation & Tutorial Improvements

  • Effect Path API Restructure: The Effect Path API chapter is reorganised into five sub-chapters (Quickstart, Core Paths, Optics Integration, Advanced Paths, Reference) so a Java developer reaches runnable Effect Path code in under five minutes without advanced material blocking the beginner path. New API-level Effect Path quickstart with three runnable examples covering MaybePath, EitherPath, and ForPath
  • Manual Gradle and Maven Setup: Book-level Quickstart trimmed to lead with the recommended hkj-gradle-plugin and hkj-maven-plugin setup; full manual build-file configuration extracted to a new dedicated page so adopters who must wire dependencies by hand have one canonical reference
  • Optics Documentation Reorganised: Optics chapter restructured along Diátaxis lines: narrative pages focus on learning, while new dedicated reference pages serve returning readers. New Quickstart, Annotations at a Glance, Optic Capabilities, Conversions, Decision Trees, Compiler Errors, and Production Readiness;
  • Monad Transformers Learning Path: Transformers chapter rebuilt as a coherent learning path: new Quickstart, Transformers at a Glance, Migration Cookbook, When to Drop to Transformers, Common Errors, and Transformer Capstone.
  • Monad Transformers Hands-On Track: New tutorial journey in hkj-examples: Tutorial 01 (When Path Isn't Enough, EitherT entry), Tutorial 02 (Async with Absence, OptionalT/MaybeT), Tutorial 03 (Stacking Transformers), and Tutorial 04 (Polymorphic Capabilities). Default test task runs solutions; new tutorialTest task includes the in-progress exercises with predictable failures
  • Foundations Chapter Refresh: Foundations reframed as the engine-room tour readers reach after shipping with the Effect Path API, Optics, or Monad Transformers, with three reading paths (mechanism tour, generic-code author, library extender) anchored on a single recurring "one line, six layers" service-method example. New pages: One Line, Six Layers, Lifting the Hood (end-to-end trace through widen / dispatch / narrow with allocation costs), and Foundations FAQ (ten direct answers including comparisons with Vavr, Cyclops, Arrow-Kt, and the Valhalla question).
  • Hands-On Tutorial Refresh: Refreshed every tutorial journey: Tutorial 00 chapter anchor (One Line, Six Layers, setup-check exercise), new Capstone Journey building the chapter anchor up to a real workflow, tiered hint structure (Nudge / Strategy / Spoiler) on tutorial files, and hand-rolled per-exercise teaching prose on every @Test in every solution file in the Why this is idiomatic / Alternative / Common wrong attempt format. New tutorialProgress Gradle task counts answerRequired() placeholders across journeys and prints a per-journey progress bar

v0.4.2 (18 April 2026)

EffectBoundary, Claude Code Skills, and Spring HTTP Ergonomics

This release introduces EffectBoundary for gradual Spring adoption of Free-monad programs, delivers a complete hkj-spring order-processing showcase demonstrating the boundary pattern end-to-end, ships a suite of six Claude Code skills providing in-editor guidance to HKJ adopters, extends the Effect Path return-value handlers with @ResponseStatus honouring and a canonical @WebMvcTest slice-test recipe, widens the Effectful capability interface for cross-path error recovery, and adds EitherPath.bimap and Try.attempt(CheckedSupplier) alongside targeted bug fixes.

  • EffectBoundary: Gradual adoption boundary bridging Free programs into the Effect Path handler ecosystem via IO-target (production) and Id-target (test) interpreters. Spring integration adds @EnableEffectBoundary, @Interpreter component meta-annotation, @EffectTest slice, FreePathReturnValueHandler, and ObservableEffectBoundary (Micrometer), letting teams adopt effects module-by-module without rewriting existing code
  • Effect Boundary Showcase: Complete Spring Boot order-processing example demonstrating the full boundary pattern: three effect algebras (OrderOp, InventoryOp, NotifyOp) composed into programs, interpreters discovered as Spring beans via @Interpreter, OrderService building pure Free<F, A> programs, OrderController invoking boundary.runIO() with the existing IOPathReturnValueHandler, TestBoundary + Id pure tests running in milliseconds, full MockMvc integration tests, and ObservableEffectBoundary metrics exposed via actuator
  • Claude Code Skills Suite: Six Claude Code skills (/hkj-guide, /hkj-optics, /hkj-effects, /hkj-bridge, /hkj-spring, /hkj-arch) providing contextual guidance on Path selection, optics generation, Free monads and effect algebras, effects-optics bridging, Spring adoption ladder, and functional-core architecture; auto-triggered on keywords or invoked directly
  • @ResponseStatus Support: All nine Effect Path return-value handlers (EitherPath, MaybePath, TryPath, ValidationPath, IOPath, CompletableFuturePath, VTaskPath, FreePath, VStreamPath) now honour @ResponseStatus on handler methods via the new SuccessStatusResolver, with controller-class fallback and meta-annotation support; POSTs can return canonical 201, DELETEs can return 204 with body suppressed
  • @WebMvcTest Slice Recipe: Canonical slice-test pattern using @ImportAutoConfiguration({HkjAutoConfiguration, HkjJacksonAutoConfiguration, HkjWebMvcAutoConfiguration}) with @MockitoBean, covering Right200 and tagged-error Left404
  • Effectful Capability Widening: handleError, handleErrorWith, and guarantee now live on the sealed Effectful interface; handleErrorWith accepts Function<? super Throwable, ? extends Effectful<A>> so IOPath and VTaskPath can cross-recover while preserving the receiver's concrete type
  • EitherPath.bimap: Transform error and success values in a single call; equivalent to .mapError(errorFn).map(successFn) with laziness on the unused branch
  • Try.attempt: New Try.attempt(CheckedSupplier) entry point for Java APIs that throw checked exceptions (Files.readString, Class.forName, JDBC, reflection). CheckedSupplier<T, X extends Exception> in hkj-api declares throws X on get(), avoiding the lambda target-type ambiguity of Try.of(Supplier)
  • hkj-checker registered on testAnnotationProcessor and every source-set annotation-processor classpath via the Gradle plugin; the Maven plugin defensively appends HKJ entries to user-supplied testAnnotationProcessorPaths, resolving error: plug-in not found: HKJChecker during test compilation
  • hkj.web.either.default-error-status property now binds and takes effect (#490); legacy flat path hkj.web.default-error-status preserved as a backward-compatible alias, with end-to-end @WebMvcTest regression coverage
  • Test coverage uplift across FocusProcessor, FoldProcessor, ForComprehensionProcessor, the optics processors, EffectAlgebra/ComposeEffects/Path processors, and KindFieldAnalyser, plus a new @ExcludeFromJacocoGeneratedReport utility

v0.4.1 (8 April 2026)

Effect Handlers, Spring Observability, and Monad Transformer Enhancements

This release introduces algebraic effect handlers with annotation-driven code generation, delivers a complete payment processing example with four interpretation modes, adds FreePath for-comprehension support, extends Spring Boot integration with VTask/VStream metrics and virtual thread health monitoring, adds mapT to all monad transformers, and includes significant bug fixes for stack safety, traverse performance, and resilience patterns.

  • @EffectAlgebra - Annotation processor generating five classes per sealed interface: Kind marker + Witness, KindHelper, Functor (auto-detects mapK for CPS vs cast-through), Ops (smart constructors + Bound inner class), and abstract interpreter skeleton with exhaustive switch dispatch
  • @ComposeEffects - Annotation processor generating composition infrastructure for 2-4 effect algebras: Inject factory methods via right-nested EitherF, composed Functor, BoundSet record, and interpret() bridge method
  • @Handles - Compile-time validation that interpreter classes handle all operations in an effect algebra; reports missing handlers as errors and extra handlers as warnings
  • EitherF - Sum type for composing effect algebras via right-nesting, with Inject for embedding operations, Free.translate for program transformation, and Interpreters.combine() for 2-4 effect dispatch
  • HandleError: Free.HandleError wraps sub-programs with typed error recovery; delegates to MonadError.handleErrorWith when available, silently ignored otherwise. Supports subclass matching via Class<E> token
  • ErrorOp - Effect algebra for typed error raising within Free programs, with ErrorOps.raise() smart constructor and Bound<E, G> for composed effects
  • StateOp - Optics-native state effect algebra with 6 operations (View, Over, Assign, Preview, TraverseOver, GetState), CPS for correct functor mapping, and StateOpInterpreter/IOStateOpInterpreter interpreters
  • ProgramAnalyser - Static analysis of Free program trees: counts instructions (Suspend), recovery points (HandleError), parallel scopes (Ap), and opaque regions (FlatMapped). All counts are lower bounds.
  • Payment Processing - Complete worked example with 4 effect algebras, 13 interpreters across production (IO), testing (Id), quote (fee estimation), and audit (WriterT) modes; 12 tests and 6 tutorials
  • Effect Handlers Introduction - Motivational documentation covering the DI gap, programs-as-data, DOP connection, terminology bridge mapping FP concepts to Java equivalents, and when-to-use guidance
  • FreePath For-Comprehensions - FreePath as the 10th path type in the ForPath system, with from(), let(), focus(), par(), traverse(), sequence(), flatTraverse(), and yield() steps
  • FreePath.attempt() - Captures outcome as Either<Throwable, A>, mapping success to Right and handling errors as Left
  • mapT - New method on all 6 monad transformers (EitherT, MaybeT, OptionalT, WriterT, ReaderT, StateT) for transforming the outer monad layer without unwrapping. Custom AssertJ assertions added for WriterT, ReaderT, and StateT
  • VTask/VStream Metrics - HkjMetricsService records success/error counts and execution duration for VTaskPathReturnValueHandler and element counts for VStreamPathReturnValueHandler; metrics exposed via /actuator/hkj endpoint
  • Virtual Thread Health Indicator - Spring Boot health indicator monitoring virtual thread availability with configurable threshold
  • OpenRewrite Recipes - AddHandleErrorCaseRecipe for missing HandleError/Ap switch cases, ConvertRawFreeToFreePathRecipe for FreePath migration, DetectInjectBoilerplateRecipe for @ComposeEffects adoption
  • FList - Lightweight immutable cons-list replacing O(n^2) LinkedList copy in ListTraverse, StreamTraverse, and VStreamTraverse with O(n) cons accumulation
  • Free.foldMap stack safety: added trampolining to prevent StackOverflowError on deep program chains
  • FreeAp.foldMap stack safety: added trampolining for deep applicative trees
  • CircuitBreaker: reset failure count on success in HALF_OPEN state
  • ConstBifunctor: fix NPE in second() by applying function to second element
  • IO.raceIO: fix ClassCastException in firstVTaskSuccess for checked exceptions
  • Lazy: add reentrant-call detection to prevent infinite recursion
  • Bulkhead: add permit-release guard to prevent negative permits
  • VStreamPar.merge: join background producer thread on close to prevent thread leak
  • VStreamThrottle: replace dual AtomicLong with AtomicReference<WindowState> CAS loop
  • Free F parameter tightened from WitnessArity<?> to WitnessArity<TypeArity.Unary> across the entire hierarchy, eliminating raw type usage
  • Additional edge case tests for NavigatorClassGenerator, FocusProcessor, and ForPathStepGenerator
  • JMH Benchmarks: 7 new benchmarks for EitherF dispatch, Free.translate, HandleError overhead, ProgramAnalyser traversal, and program construction cost

v0.4.0 (22 March 2026)

SPI-Aware Path Widening, Expanded Plugin Ecosystem, and Focus DSL Restructure

This release introduces SPI-aware path widening for the Focus DSL, allowing automatic AffinePath and TraversalPath generation based on container cardinality, expands the TraversableGenerator plugin ecosystem to 23 generators across 6 library families, adds Traversal.asFold() for read-only monoidal aggregation, restructures the Focus DSL documentation into dedicated pages, and delivers comprehensive test coverage and Javadoc quality improvements across processor modules.

  • SPI-Aware Path Widening: Automatic path type inference based on container cardinality: ZERO_OR_ONE produces AffinePath, ZERO_OR_MORE produces TraversalPath, eliminating manual .each() and .some() calls in generated navigators
  • Cardinality-Based Widening: TraversableGenerator SPI extended with Cardinality enum, priority system (PRIORITY_FALLBACK, PRIORITY_DEFAULT, PRIORITY_OVERRIDE), widenCollections opt-in attribute, and wildcard type resolution for ? extends T, ? super T, and bare ?
  • Nested Container Widening: Compound types like Optional<List<String>> resolve correctly through recursive cardinality analysis, with navigator field collision detection
  • Generator Plugin Ecosystem: 23 TraversableGenerator implementations across 6 library families: base JDK (Array, List, Set, Optional, MapValue), Apache Commons Collections4 (HashBag, UnmodifiableList), Eclipse Collections (ImmutableBag, MutableBag, ImmutableList, MutableList, ImmutableSet, MutableSet, ImmutableSortedSet, MutableSortedSet), Google Guava (ImmutableList, ImmutableSet), Vavr (List, Set), and HKJ native (Either, Maybe, Try, Validated)
  • Traversal.asFold(): Conversion from any Traversal to a read-only Fold for monoidal aggregation, existence checks, and length counting via new ConstForFold applicative functor
  • AffinePath: New AffinePath<S, A> for zero-or-one navigation in Focus DSL, with Affine optic interface supporting getOrModify and set
  • Portfolio Risk Analysis: Capstone example demonstrating container navigation, SPI widening, and nested optics composition
  • Focus DSL documentation restructured into dedicated pages: Containers, Navigation, Effects, and Reference
  • Tutorial 19: Navigator Generation: 7 exercises on annotation-driven navigator code generation
  • Tutorial 20: Container Navigation: 4 exercises on SPI-aware container type navigation
  • Automated SPI service declarations via Avaje SPI processor for plugin discovery

v0.3.7 (15 March 2026)

WriterT Transformer, For-Comprehension Power-Ups, Build Tooling, and Spring Virtual Thread Support

This release introduces the WriterT monad transformer with MTL-style capability interfaces, enriches for-comprehensions with parallel composition, traversal operations, and optics integration, adds compile-time Path type checking via the new hkj-checker javac plugin, delivers one-line project setup through build tool plugins (hkj-gradle-plugin and hkj-maven-plugin), and extends Spring MVC with virtual-thread-native return value handlers for VTaskPath and VStreamPath.

  • WriterT: Monad transformer for output accumulation across effect boundaries, wrapping Kind<F, Pair<A, W>> with automatic Monoid-based combining during flatMap chains
  • MonadWriter: MTL-style capability interface with tell, listen, pass, listens, and censor for output accumulation
  • MonadReader: MTL-style capability interface with ask, local, reader, and asks for shared environment access
  • MonadState: MTL-style capability interface with get, put, modify, and gets for stateful computation
  • par(): Parallel/applicative composition for For and ForPath comprehensions; true concurrency on VTask, intent-documenting on sequential monads
  • traverse/sequence/flatTraverse: Bulk effectful operations within comprehension chains: apply an effectful function across a structure, flip Structure<Effect<A>> to Effect<Structure<A>>, or traverse-and-flatten in one step
  • For-Comprehension Optics Integration: through(Iso) for type-safe value conversion in For; traverseOver(), modifyThrough(), modifyVia(), and updateVia() for optics-driven state operations in ForState
  • Fold Combinators: Fold.plus(), Fold.empty(), and Fold.sum() forming a monoid on folds for multi-path data extraction
  • Compile-Time Path Checks: hkj-checker javac plugin detecting Path type mismatches at compile time for via, then, zipWith, zipWith3, recoverWith, and orElse
  • Build Plugins: hkj-gradle-plugin (one-line Gradle setup) and hkj-maven-plugin (Maven lifecycle extension) that auto-configure HKJ dependencies, --enable-preview flags, compile-time checking, and optional Spring Boot integration
  • hkj-bom: Bill of Materials POM for version-aligned dependency management across all HKJ modules in both Gradle and Maven
  • Diagnostics: hkjDiagnostics Gradle task and mvn hkj:diagnostics goal reporting active dependencies, compiler arguments, and checks
  • VTaskPath Spring MVC: VTaskPathReturnValueHandler converting controller return values to async DeferredResult responses on virtual threads
  • VStreamPath SSE: VStreamPathReturnValueHandler converting controller return values to Server-Sent Events with pull-based backpressure, no Reactor required
  • Dependency updates: Gradle 9.4.0, JUnit 6.0.3, Jackson 3.1.0, Spring Boot 4.0.3, jOOQ 3.20.11, javapoet 0.12.0, and others
  • Faster FunctionValidator and KindValidator with simplified implementation
  • Test reliability improvements: replaced Thread.sleep with Awaitility across test suite

v0.3.6 (6 March 2026)

VStream Lazy Streaming, Resilience Patterns, and ForState Comprehensions

This release introduces VStream, a lazy pull-based streaming type built on virtual threads with full HKT integration, adds four core resilience patterns (Circuit Breaker, Bulkhead, Retry, Saga) with Effect Path integration, extends ForState with filtering and pattern matching, and delivers a Market Data Pipeline capstone example.

  • VStream: Lazy pull-based streaming on virtual threads with Step protocol (Emit/Done/Skip), factory methods (of, range, iterate, generate, unfold), transformation combinators, and error recovery
  • VStream HKT Integration: VStreamKind witness type with Functor, Applicative, Monad, Foldable, Traverse, and Alternative type class instances
  • VStream Parallel Operations: VStreamPar with parEvalMap, parEvalMapUnordered, parEvalFlatMap, merge, parCollect, and chunking combinators
  • VStream Resources: bracket/onFinalize resource lifecycle management and VStreamReactive bidirectional Flow.Publisher bridge with backpressure
  • VStreamPath: Effect Path bridge with factory methods, PathOps operations, terminal operations bridging to VTaskPath, and optics focus bridge
  • Circuit Breaker: State machine (Closed/Open/HalfOpen) with configurable failure thresholds and recovery timeouts
  • Bulkhead: Concurrency limiting for isolating resource access
  • Retry: Configurable retry policies with fixed delay, exponential backoff, and jitter
  • Saga: Distributed transaction compensation with ordered rollback
  • Combined Resilience: Composing multiple resilience patterns and Path API ergonomic methods: retry(), circuitBreaker(), bulkhead(), timeout()
  • ForState: Filtering (when), pattern matching (matchThen), traversals, zoom, and toState() bridge from For comprehensions at all arities (1–12)
  • traverseWith(): Parallel effectful optics traversal for FocusPath, AffinePath, and TraversalPath via VTaskPath and StructuredTaskScope
  • Market Data Pipeline: 14-feature capstone example demonstrating concurrent feed merging, parallel enrichment, risk assessment, windowed aggregation, anomaly detection, and circuit breaker failover
  • Refreshed Monads chapter with problem-first structure, real-world analogies, and consistent formatting
  • FunctionValidator optimisation: deferred error-message construction avoids String allocation on the happy path; fixed Gradle benchmark commands (-Pincludes)
  • Javadoc generation fix to include annotation-processor-generated sources (Tuple2Tuple12, MonadicSteps, etc.)
  • JMH benchmarks for VStream construction, combinators, terminals, and parallel operations

v0.3.5 (15 February 2026)

Extended For-Comprehensions, VTask API Refinement, and Documentation Restructure

This release extends for-comprehension arity to 12, simplifies the VTask API, adds Maybe-to-Either conversions, upgrades to JUnit 6, and delivers a comprehensive documentation restructure with quickstart guides, cheat sheets, migration cookbooks, and railway diagrams.

  • For-Comprehension Arity 12: For and ForPath now support up to 12 monadic bindings (previously 5), with generated Tuple9Tuple12 and Function9Function12
  • VTask API: VTask.run() no longer declares throws Throwable; checked exceptions are wrapped in VTaskExecutionException
  • Maybe.toEither: New toEither(L) and toEither(Supplier<L>) conversion methods for seamless MaybeEither transitions
  • Quickstart: New getting-started guide with Gradle and Maven setup including --enable-preview configuration
  • Cheat Sheet: Quick-reference for Path types, operators, escape hatches, and type conversions
  • Stack Archetypes: 7 named transformer stack archetypes with colour-coded railway diagrams
  • Migration Cookbook: 6 recipes for migrating from try/catch, Optional chains, null checks, CompletableFuture, validation, and nested records
  • Compiler Error Guide: Solutions for the 5 most common Effect Path compiler errors
  • Effects-Optics Capstone: Combined effects and optics pipeline example
  • Railway operator diagrams for all 8 Effect Path operators and for EitherT, MaybeT, OptionalT transformers
  • JUnit 6.0.2 upgrade (from 5.14.1) across all test modules
  • Golden file test infrastructure with automated sync verification and pitest mutation coverage improvements

v0.3.4 (31 January 2026)

External Type Optics and Examples Gallery

This release introduces powerful optics generation for external types you cannot modify, plus a new Examples Gallery chapter documenting all runnable examples.

  • @ImportOptics: Generate optics for JDK classes and third-party library types via auto-detection of withers and accessors
  • Spec Interfaces: Fine-grained control over external type optics with OpticsSpec<S> for complex types like Jackson's JsonNode
  • @ThroughField Auto-Detection: Automatic traversal type detection for List, Set, Optional, arrays, and Map fields
  • Examples Gallery: New chapter with categorised, runnable examples demonstrating core types, transformers, Effect Path API, and optics
  • Comprehensive hkj-processor testing improvements with enhanced coverage

v0.3.3 (24 January 2026)

Structured Concurrency, Atomic Optics, and Enhanced Examples

This release introduces structured concurrency primitives, atomic coupled-field updates, and a comprehensive Order Workflow example demonstrating these patterns.

  • Structured Concurrency: Scope for parallel operations with allSucceed(), anySucceed(), firstComplete(), and accumulating() joiners
  • Resource Management: Resource for bracket-pattern cleanup with guaranteed release
  • Coupled Fields: Lens.paired for atomic multi-field updates bypassing invalid intermediate states
  • Order Workflow Overview: Reorganised documentation with focused sub-pages
  • Concurrency and Scale: Context, Scope, Resource, VTaskPath patterns in practice
  • EnhancedOrderWorkflow: Full workflow demonstrating Context, Scope, Resource, VTaskPath
  • OrderContext: ScopedValue keys for trace ID, tenant isolation, and deadline enforcement
  • Scope & Resource Tutorials: 18 exercises on concurrency patterns
  • Release History: New page documenting all releases

v0.3.2 (17 January 2026)

Virtual Thread Concurrency with VTask

This release introduces VTask<A>, a lazy computation effect leveraging Java 25's virtual threads for lightweight concurrent programming.

  • VTask: Lazy computation effect for virtual thread execution
  • Structured Concurrency: Scope for parallel operations with allSucceed(), anySucceed(), and accumulating() patterns
  • Resource Management: Resource for bracket-pattern cleanup guarantees
  • VTaskPath: Integration with the Effect Path API
  • Concurrency and Scale: Practical patterns in the Order Workflow example
  • Par parallel combinators for concurrent execution
  • Comprehensive benchmarks comparing virtual vs. platform threads

v0.3.1 (15 January 2026)

Static Analysis Utilities

This release adds utilities for statically analysing Free Applicative and Selective functors without execution.


v0.3.0 (4 January 2026)

Effect Path Focus Integration

Major release introducing the unified Effect Path API and Focus DSL integration. Requires Java 25 baseline.


Earlier Releases

v0.2.8 (26 December 2025)

  • Introduced ForPath for Path-native for-comprehension syntax
  • Complete Spring Boot 4.0.1 migration of hkj-spring from EitherT to Effect Path API
  • New return value handlers for Spring integration

v0.2.7 (20 December 2025)

  • Effect Contexts: ErrorContext, OptionalContext, ConfigContext, MutableContext
  • Bridge API enabling seamless transitions between optics and effects

v0.2.6 (19 December 2025)

  • New Effect Path API with 17+ Path types
  • Retry policies and parallel execution utilities
  • Kind field support in Focus DSL

v0.2.5 (9 December 2025)

  • Annotation-driven Focus DSL for fluent optics composition
  • Free Applicative and Coyoneda functors
  • Natural Transformation support

v0.2.4 (3 December 2025)

  • Affine optic for focusing on zero or one element
  • ForTraversal, ForState, and ForIndexed comprehension builders
  • For-comprehension and optics integration

v0.2.3 (1 December 2025)

  • Cross-optic composition (Lens + Prism → Traversal)
  • Experimental Spring Boot starter
  • Custom target package support for annotation processor

v0.2.2 (29 November 2025)

  • Java 25 baseline
  • Experimental hkj-spring module
  • Validation helpers and ArchUnit architecture tests
  • Thread-safety fix in Lazy memoisation

v0.2.1 (23 November 2025)

  • 7-part Core Types tutorial series
  • 9-part Optics tutorial (~150 minutes total)
  • Versioned documentation system
  • Property-based testing infrastructure
  • JMH benchmarking framework

v0.2.0 (21 November 2025)

  • Six new optic types: Fold, Getter, Setter, and indexed variants
  • FreeMonad for DSL construction
  • Trampoline for stack-safe recursion
  • Const Functor
  • Enhanced Monoid with new methods
  • Alternative type class

v0.1.9 (14 November 2025)

  • Selective type class for conditional effects
  • Enhanced optics with modifyWhen() and modifyBranch()
  • Bifunctor for Either, Tuple2, Validated, and Writer
  • Higher-kinded Stream support

v0.1.8 (9 September 2025)

  • Profunctor type class
  • Profunctor operations in universal Optic interface

v0.1.7 (29 August 2025)

  • Generated with* helper methods for records via @GenerateLenses
  • Traversals.forMap() for key-specific Map operations
  • Semigroup interface with Monoid extending it
  • Validated Applicative with error accumulation

v0.1.6 (14 July 2025)

  • Optics introduction: Lens, Iso, Prism, and Traversals
  • Annotation-based optics generation
  • Plugin architecture for extending Traversal types
  • Modular release structure

v0.1.5 (12 June 2025)

  • For comprehension with generators, bindings, guards, and yield
  • Tuple1-5 and Function5 support

v0.1.4 (5 June 2025)

  • Validated Monad
  • Standardised widen/narrow pattern for KindHelpers (breaking change)

v0.1.3 (31 May 2025)

  • First Maven Central publication
  • 12 monads, 5 transformers
  • Comprehensive documentation

v0.1.0 (3 May 2025)

  • Initial release
  • Core types: Either, Try, CompletableFuture, IO, Lazy, Reader, State, Writer
  • EitherT transformer

See Also


Previous: Glossary Next: Benchmarks & Performance