# Upgrading

Sections run newest first: start at the top and read down to the section for the release after the one you are on. Each lists what the release notes flag as able to stop a build that compiled, or to change what a program does. [Removals in 0.7.0](#removals-in-070) and [Removals in 0.6.0](#removals-in-060) list the APIs due to go in those releases, with the recipe that migrates each, and [Removals in 0.5.0](#removals-in-050) those 0.5.0 removed.

---

## Removals in 0.7.0 {#removals-in-070}

Each of these compiles with javac's `[removal]` warning, which fails a `-Werror` build. The `parCollect` forms each gave the list `toList()` gives: a stream is read one pull at a time, so they ran nothing in parallel. The `org.higherkindedj.openrewrite.MigrateDeprecationsTo0_7_0` recipe runs every recipe the table names, and marks each call it cannot rewrite safely, as [0.7.0 deprecation migration](../tooling/openrewrite.md#070-deprecation-migration) explains.

| Deprecated | Since | Use instead | Recipe |
|---|---|---|---|
| `VStream.closeAfterFailure(stream, failure)` | 0.6.0 | `stream.close()`; a failed `mapTask` task gives a `Step.Fail`, whose tail you close as an `Emit`'s | By hand |
| `VStreamPar.parCollect(stream, batchSize)` | 0.6.0 | `stream.toList()` | `ReplaceParCollectWithToListRecipe` |
| `VStreamPath.parCollect(batchSize)` | 0.6.0 | `toList()` on the path | `ReplaceParCollectWithToListRecipe` |
| `PathOps.parCollectVStream(path, batchSize)` | 0.6.0 | `path.toList()` | `ReplaceParCollectWithToListRecipe` |
| `Try.foldFailureFirst` and `TryPath.foldFailureFirst` | 0.6.0 | `fold(failureMapper, successMapper)`, the same two functions in the same order | `RenameTryFoldFailureFirst` |
| `Operation.FOLD_FAILURE_FIRST` | 0.6.0 | `Operation.FOLD`, which `Try.fold` reports | By hand |
| `NaturalTransformation<F, G>`, in `org.higherkindedj.hkt.effect` | 0.6.0 | `Natural<F, G>`, which it extends | `ReplaceNaturalTransformationWithNatural` |
| `FreePath.foldMapWith(interpreter, monad)` | 0.6.0 | `foldMap(interpreter, monad)` | `ReplaceNaturalTransformationWithNatural` |
| `FreeApPath.foldMapWith(interpreter, applicative)` | 0.6.0 | `foldMapKind(interpreter, applicative)` | `ReplaceNaturalTransformationWithNatural` |

---

## Removals in 0.6.0 {#removals-in-060}

Each of these compiles with javac's `[removal]` warning, which fails a `-Werror` build. A Focus path shortcut's replacement is its typed form: the same verb, given an instance that reads the element type from the path. So a typed form fits only a path that focuses on its container exactly. The `org.higherkindedj.openrewrite.MigrateDeprecationsTo0_6_0` recipe runs every recipe the table names, and marks each call a typed form does not fit rather than rewriting it. [0.6.0 deprecation migration](../tooling/openrewrite.md#060-deprecation-migration) gives each such call its replacement.

| Deprecated | Since | Use instead | Recipe |
|---|---|---|---|
| A Focus path's `each()`, on `FocusPath`, `AffinePath`, `TraversalPath` and a navigator | 0.5.0 | `each(EachInstances.listEach())` | `ReplaceUntypedPathShortcutsRecipe` |
| A Focus path's `some()` | 0.5.0 | `some(FocusPaths.optionalSome())` | `ReplaceUntypedPathShortcutsRecipe` |
| A Focus path's `at(index)` | 0.5.0 | `via(FocusPaths.listAt(index))` | `ReplaceUntypedPathShortcutsRecipe` |
| A Focus path's `atKey(key)` | 0.5.0 | `via(FocusPaths.mapAt(key))` | `ReplaceUntypedPathShortcutsRecipe` |
| A Focus path's `traverseOver(traverse)` | 0.5.0 | `each(EachInstances.fromTraverse(traverse))` | `ReplaceUntypedPathShortcutsRecipe` |
| A Focus path's `head()`, `last()`, `tail()` and `init()` | 0.5.0 | `via(FocusPaths.listHead())`, `via(FocusPaths.listLast())`, `via(FocusPaths.listTail())` and `via(FocusPaths.listInit())` | `ReplaceUntypedPathShortcutsRecipe` |
| A Focus path's `cons()` and `headTail()` | 0.5.0 | `via(FocusPaths.listCons())` and `via(FocusPaths.listHeadTail())` | `ReplaceUntypedPathShortcutsRecipe` |
| A Focus path's `snoc()` and `initLast()` | 0.5.0 | `via(FocusPaths.listSnoc())` and `via(FocusPaths.listInitLast())` | `ReplaceUntypedPathShortcutsRecipe` |
| `ScopeJoiner.resultEither()` | 0.5.0 | `joinEither()` on the `Scope`, in place of `join()`; for a `StructuredTaskScope` you open yourself, the value its `join()` returns, or the cause of the `FailedException` it throws | By hand |

---

## Removals in 0.5.0 {#removals-in-050}

0.5.0 removed each of these. The `org.higherkindedj.openrewrite.MigrateDeprecationsTo0_5_0` recipe runs every recipe the table names, and finds each removed API only while the project is on 0.4.x, so run it before you move. See [0.5.0 deprecation migration](../tooling/openrewrite.md#050-deprecation-migration).

| Deprecated | Since | Use instead | Recipe |
|---|---|---|---|
| `KindValidator.narrowWithPattern` | 0.4.4 | `KindValidator.narrowHolder` | `RenameKindValidatorNarrowWithPattern` |
| `StateTKind.narrowK` | 0.4.5 | `StateTKind.narrow` | `RenameStateTKindNarrowK` |
| `StateT.evalStateT(state)` and `execStateT(state)`, and the `StateTKindHelper` forms without a monad | 0.4.6 | The overloads that take the `Monad<F>` | By hand |
| `StateT.monadF()` | 0.4.6 | The outer `Monad<F>` you built the stack with, kept in your own reference | By hand |
| `Try.fold` and `TryPath.fold`, success first | 0.4.6 | `foldFailureFirst(failureMapper, successMapper)`, or from 0.6.0 `fold(failureMapper, successMapper)` | `SwapTryFoldToFoldFailureFirstRecipe` |
| `Each.eachWithIndex()` | 0.4.7 | Narrow to `EachIndexed` and call `indexedTraversal()` | By hand |
| `@PathConfig` | 0.4.11 | Nothing, since it has no effect; to rename a Path, set `suffix` on `@PathSource` | `RemovePathConfig` |
| `@PathSource` capability `EFFECTFUL` | 0.4.11 | `CHAINABLE`, which generates the same | `ReplaceDeprecatedPathSourceCapabilitiesRecipe` |
| `@PathSource` capability `ACCUMULATING` | 0.4.11 | `RECOVERABLE`, which generates the same | `ReplaceDeprecatedPathSourceCapabilitiesRecipe` |

`StateT` also changes shape in 0.5.0: its `monadF` record component goes, so two `StateT` values with the same state function compare equal whichever `Monad` built them. Its `equals`, `hashCode` and `toString` change with it. Its constructor, `StateT.create` and `StateTKindHelper.stateT` take the state function alone, and `StateT.mapT` takes only the transformation. The `RemoveStateTMonadArgument` recipe drops the `Monad` argument from each call. It ships in `hkj-openrewrite` 0.5.0 and joins the same group, so run the group before your project moves off 0.4.11. A `StateT` held in `var` over a witness with a type argument, such as `EitherKind.Witness<E>`, then needs its types named: `StateT.<S, EitherKind.Witness<E>, A>create(fn)`.

`fold` returns on `Try` and `TryPath` in 0.6.0, failure first. Going straight to 0.6.0 by hand, find each call while still on 0.4.x, where javac's deprecation warning names it. Swap its two functions in the change that moves to 0.6.0; on 0.5.x the name is `foldFailureFirst`. [To 0.6.0](#to-060) says why.

---

## To 0.6.0 {#to-060}

This release is not out yet. [Upgrading from 0.5.0](unreleased.md#upgrading) collects what it changes so far, and [Removals in 0.6.0](#removals-in-060) lists the APIs it is due to remove, with the recipe that migrates each.

0.6.0 brings back `fold` on `Try` and `TryPath`, failure first, so a success-first `fold` left over from 0.4.x compiles again wherever both its functions accept either argument, and runs the two the wrong way round. On a `Try<String>`, `t.fold(v -> true, e -> false)` gave `true` for a success on 0.4.x, and gives `true` for a failure on 0.6.0. A constant, `Object::toString` or `String::valueOf` in either place does the same. Nothing warns you, so a project on 0.4.x takes these steps in order, with `hkj-openrewrite` 0.6.x for each run:

1. While the project still compiles against 0.4.x, run `MigrateDeprecationsTo0_5_0`, which swaps each success-first `fold` into `foldFailureFirst`, as [To 0.5.0](#to-050) says. Under `-Werror`, run `MigrateDeprecationsTo0_6_0` in the same pass.
2. Replace each jar compiled against 0.4.x: rebuild it from migrated source, or take a release of it built against 0.5.0 or later. Such a jar calls the same method signature, so on 0.6.0 it runs its `fold` calls swapped, or fails with a `ClassCastException` inside one, where on 0.5.0 it threw `NoSuchMethodError`.
3. Move to 0.6.0, then run `MigrateDeprecationsTo0_7_0`, which renames each `foldFailureFirst` the swap wrote to `fold`. 0.6.0 deprecates `foldFailureFirst`, so until then a `-Werror` build fails, and a rewrite plugin that compiles the project first needs `-Werror` lifted for the run.

Already moved without step 1? Run it on your last commit on 0.4.x, or check each `fold` on a `Try` or `TryPath` by hand. On 0.4.x the recipe swaps every `fold`, one written for 0.6.0 included.

---

## To 0.5.0 {#to-050}

The 0.5.0 notes give the details in [Upgrading from 0.4.11](v0_5_0.md#upgrading). Run `MigrateDeprecationsTo0_5_0` first, while the project is still on 0.4.11, since its recipes find the APIs in [Removals in 0.5.0](#removals-in-050) only there. [Before you upgrade](v0_5_0.md#before-you-upgrade) lists that step and the others to take first. Then read [What a running program can notice](v0_5_0.md#runtime-changes), most of it about null values, because the compiler cannot point at those changes. Each entry in [What stops a build that compiled](v0_5_0.md#build-changes) fails at a line the message names, with its fix. That table's entries for a nullness checker leave out a lambda that returns null through `VTask.of`, `Try.of`, `IO.delay` or another factory taking a JDK `Supplier` or `Callable`. NullAway flags one only with the two options in [What a nullness checker sees](../hkts/core-concepts.md#what-a-nullness-checker-sees).

---

## To 0.4.11 {#to-0411}

The 0.4.11 notes give the details in [Upgrading from 0.4.10](v0_4_11.md#upgrading). Read [Before you upgrade](v0_4_11.md#before-you-upgrade) first, since a library that publishes specs must be rebuilt before the builds that consume it. Then read [What a running program can notice](v0_4_11.md#runtime-changes), because the processor cannot point at those changes. Each entry in [What stops a build that compiled](v0_4_11.md#build-changes) fails at a line the message names, with its fix.

---

## To 0.4.10 {#to-0410}

The details are in each group of the [0.4.10 notes](v0_4_10.md).

- **A sparse PATCH scans a same-typed container for `null`**: a `null` element, which used to reach the domain, now reports as `tags.1: must not be null`.
- **Explicit type witnesses on some `@ImportOptics` methods stop compiling**: a generated method's type parameters can fall, as `OpticsSpec<Pair<A, String>>` now generates `<A>`. Drop the witnesses: the inferred result is unchanged.
- **The processor refuses some `@InstanceOf` focuses**: one naming a type argument the source does not pin, and a parameterised member of a generic type.
- **A resolved `copyConstructor` is emitted as a cast**: the cast can select a different constructor than ran in 0.4.9, so check overloaded types with `LensLaws`.
- **Generated signatures carry `@Nullable`**: a nullness checker in your build can report differently.
- **`@GenerateFocus` refuses a raw or wildcard `Set` or `Collection`**: such a component used to throw `ClassCastException` on first use. Name the type argument.
- **Three navigator return types move to what the static method reports**: a `Collection` subtype such as `ArrayList` is a `FocusPath` over the container. A nested `List<Optional<String>>` composes to the leaf, so drop a trailing `.some()`.
- **An SPI container of non-navigable elements stops at the container**: set `widenCollections = true` on the declaring record to widen it as before.
- **Four more `@Nullable` annotations widen to `AffinePath`**: JSpecify's, JetBrains', AndroidX's and SpotBugs'. Read such a component with `getOptional`.
- **`Traversals.forSet()` and `traverseSet` return an unmodifiable set**: they used to return a mutable `LinkedHashSet`.
- **A varargs bridge method takes a bare `null` only with a cast**: write `(String[]) null`.
- **`@GeneratePathBridge` refuses more shapes, and warns on an empty interface**: it refuses a raw effect return type, a wildcard `Validated` error type, and a `static` or `private` `@PathVia` method. An interface with no `@PathVia` method draws a warning, which fails a `-Werror` build.
- **A `@ComposeEffects` support class changes shape**: each field must be a `Class<XOp<?>>` naming an `@EffectAlgebra`, and `BoundSet<F>` becomes `BoundSet`. Fix any field the processor refuses, then recompile, and the last effect dispatches at its correct depth.
- **`discarded-effect` reports a deferred Path dropped as a statement**: such as `Path.io(() -> 1).peek(log)`. The check is an error by default, so such a statement stops the build.

---

## To 0.4.9 {#to-049}

The details are in the [0.4.9 notes](v0_4_9.md).

- **An all-`FieldError` response is a 422, not a 400** ([#627](https://github.com/higher-kinded-j/higher-kinded-j/issues/627)): set `hkj.web.validation-field-error-status: 400` to keep the old status.
- **A `null` on a wire parses as a located error**: `parse` and a fallible `assemble` used to throw `NullPointerException`. At a Spring boundary a 500 becomes a 422.
- **A list element's failure carries its index**: `emails: not an email address` becomes `emails.1: not an email address`.
- **A leaf naming no domain component is a compile error**: so is a spec method that collides with a generated member ([#654](https://github.com/higher-kinded-j/higher-kinded-j/issues/654)).
- **A leaf on a projected component now takes effect**: the projection then offers the validated `patch` in place of `asLens()`, so a call to `asLens()` stops compiling.
- **Unused `hkj-spring` configuration properties are removed** ([#642](https://github.com/higher-kinded-j/higher-kinded-j/issues/642)): delete the keys, since none changed behaviour. Code that read them programmatically stops compiling.
- **`hkj.security.validated-user-details` defaults to `false` and starts empty** ([#642](https://github.com/higher-kinded-j/higher-kinded-j/issues/642)): the sample accounts moved to `ValidatedUserDetailsService.withSampleUsers()`, so enabling it without registering accounts fails every login.
- **A JWT with a missing or malformed authorities claim is a 401** ([#642](https://github.com/higher-kinded-j/higher-kinded-j/issues/642)): set `hkj.security.reject-missing-authorities-claim: false` to accept a missing claim.
- **An SSE response commits after the stream's first element** ([#642](https://github.com/higher-kinded-j/higher-kinded-j/issues/642)): a stream failing at its start returns the configured failure status. Emit an early heartbeat on a stream that stays idle.
- **Ambiguous effect-boundary interpreters fail at startup** ([#642](https://github.com/higher-kinded-j/higher-kinded-j/issues/642)): the choice used to follow scan order silently.

---

## To 0.4.1 through 0.4.8 {#to-041-048}

Besides the deprecations in [Removals in 0.5.0](#removals-in-050), the notes for these releases flag these changes.

- **0.4.1: `Free` gains `HandleError` and `Ap` cases**: an exhaustive `switch` over `Free` stops compiling until it handles them, and `AddHandleErrorCaseRecipe` finds such a switch. `Free`'s `F` bound tightens to `WitnessArity<TypeArity.Unary>`. See the [0.4.1 notes](v0_4_1.md).
- **0.4.2: Effect Path handlers honour `@ResponseStatus`**: an annotated handler method now answers with its status, such as `201` for a POST. See the [0.4.2 notes](v0_4_2.md).
- **0.4.2: `hkj.web.either.default-error-status` takes effect** ([#490](https://github.com/higher-kinded-j/higher-kinded-j/issues/490)): the property used not to bind. The flat `hkj.web.default-error-status` still works as an alias.
- **0.4.3: error class names match status keywords by whole word**: `RevalidationError` no longer matches the `validation` heuristic, so its status can change. See the [0.4.3 notes](v0_4_3.md).
- **0.4.5: the compile-time checker gains eleven checks**: the build plugins enable it, so a finding at error stops the build and one at warning fails a `-Werror` build. See [Compile-Time Checks](../tooling/compile_checks.md).
- **0.4.5: every Effect Path prints one `toString` form** ([#530](https://github.com/higher-kinded-j/higher-kinded-j/issues/530)): a test that compares the old text fails. See the [0.4.5 notes](v0_4_5.md).
- **0.4.7: the checker's `raw-kind` check warns on a raw `Kind`**: the warning fails a `-Werror` build. See the [0.4.7 notes](v0_4_7.md).
- **0.4.7: `recoverWith` rejects a `null` argument at once** ([#553](https://github.com/higher-kinded-j/higher-kinded-j/issues/553)): on every `MonadError`, where some instances used to fail later.

---

## To 0.3.5 {#to-035}

- **`VTask.run()` no longer declares `throws Throwable`**: it wraps a checked exception in `VTaskExecutionException`, so code that caught a checked exception from `run()` catches the wrapper instead. See the [0.3.5 notes](v0_3.md#v035-15-february-2026).

---

## To 0.3.0 {#to-030}

- **`Kind` carries its arity from 0.3.0**: type parameters used as witnesses need `WitnessArity` bounds, and the `AddArityBounds` recipe adds them. See [Arity migration](../tooling/openrewrite.md#arity-migration-02x-to-030) and the [0.3.0 notes](v0_3.md#v030-4-january-2026).

---

**Previous:** [Release History](../release-history.md)
**Next:** [Unreleased: 0.6.0](unreleased.md)
