# v0.4.7 (26 June 2026) {#v047-26-june-2026}

[v0.4.7 on GitHub](https://github.com/higher-kinded-j/higher-kinded-j/releases/tag/v0.4.7)

**`@HkjHttpClient`: generated Effect-Path HTTP clients**

`hkj-spring` was server-side only: the `*PathReturnValueHandler`s 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](../spring/declarative_http_clients.md) 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](https://github.com/higher-kinded-j/higher-kinded-j/pull/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](https://github.com/higher-kinded-j/higher-kinded-j/issues/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](https://github.com/higher-kinded-j/higher-kinded-j/issues/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](https://github.com/higher-kinded-j/higher-kinded-j/issues/560)).
- `Maybe`/`Either`/`Validated` roots now extend their `Kind` interfaces, making the five `widen`/`widen2` methods cast-free upcasts ([#561](https://github.com/higher-kinded-j/higher-kinded-j/issues/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](https://github.com/higher-kinded-j/higher-kinded-j/issues/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](https://github.com/higher-kinded-j/higher-kinded-j/issues/562)).
- Consolidated `Free.foldMap`'s two stack-safe interpreters behind the single `Natural` path ([#563](https://github.com/higher-kinded-j/higher-kinded-j/issues/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](https://github.com/higher-kinded-j/higher-kinded-j/issues/564)). See [Each type class](../optics/each_typeclass.md) / [Indexed Optics](../optics/indexed_optics.md).

**`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](https://github.com/higher-kinded-j/higher-kinded-j/issues/565)). Documented in [Compile-Time Checks](../tooling/compile_checks.md).

---

**Previous:** [v0.4.8](v0_4_8.md)
**Next:** [v0.4.6](v0_4_6.md)
