v0.5.0 (26 October 2026)
This release makes the optics generated for a record easier to reach and safer to hold. Navigators are on by default, so OrderFocus.lines().quantity() chains through a list from a bare @GenerateFocus, and every optic and Focus path takes a @Nullable focus. The core types now refuse null as a success, as an error and as a lazy value, so a null fails where it is made rather than where it is read. Resources are released exactly once, whatever stops the work. Every API deprecated for removal in 0.5.0 is gone, and MigrateDeprecationsTo0_5_0 migrates most calls.
- Navigators are on by default, and a navigator is a path.
UserFocus.address().street()andOrderFocus.lines().quantity()work with a bare@GenerateFocus, and the navigator goes wherever a path goes. - Optics over a nullable field pass a nullness checker. Every optic and Focus path type takes a
@Nullablefocus, and a bridge into an Effect Path can say what a null focus becomes. - No success, error or lazy value is null.
Either,Try,IO,VTask,VStreamand every Path over them refuse null as a success, an error or a lazy value, andUnitstands for a step with nothing to return. - Resources are released exactly once. A
VStream, aResource, abracketand aScopeclean up whatever stops them: an earlytake, a failure, a timeout or a cancellation. - Optics cost less. A read through a traversal no longer rebuilds what it reads, a fold stops at its answer, and a Focus path rebuilds a 100,000-element list in about a millisecond.
- Every API deprecated for removal in 0.5.0 is gone.
MigrateDeprecationsTo0_5_0migrates most calls, and the build table gives the fix for the rest. A Focus path's untyped shortcuts are deprecated for removal in 0.6.0, andMigrateDeprecationsTo0_6_0replaces them. - The Optics chapter is rewritten. It reads in order from a first nested update to a PATCH, on one order service, with every example compiled and checked.
- Some changes alter what a running program does. Many others stop the build, so read Upgrading from 0.4.11 first.
Mapping
Generic specs
- A bounded generic spec covers only what its bound admits (#1118). A spec over
Page<T>withT extends Numbercovered aPage<String>field and broke the generated code. That field now reports no usable source, naming the bound, or nests through a spec forPage<String>declared beside it. See A bounded spec covers only what its bounds admit. - A generic spec over an array of its parameter leaves a primitive array alone (#1091). No type argument is a primitive, so a spec over
Page<T[]>no longer covers aPage<int[]>field, which crashed the processor or failed in generated code. The field reports no usable source instead. See The boundaries of a generic spec. - An element-mapped spec nests where a type argument passes through unconverted (#1122). A spec over
TaggedPage<T, X>whose leaf converts only the elements broke the generated code wherever it nested, since no leaf namesX. The generated call now spells out its type arguments, keeping a@Nullablethe records declare. See How an element-mapped spec nests. - A merge fills a component through a generic spec (#1123). A threaded spec such as
PageMapping<T>now fills a merge as it nests in a mapping. An element-mapped spec is not supported yet, and the merge says so, offering the leaf that builds it. See How a merge fills. - The most specific of several specs for a pair maps it (#1123).
TagPageMappingbesidePageMapping<T>, orPageMapping<T>besideCodecPageMapping<T, TDto>, made a field ambiguous. The more specific now maps it, and a compiler note names a dependency's spec it displaced. See The most specific spec for a pair is used.
Beans, builders and codecs
- More Lombok
@Singularcollections map (#1110). A build-only collection whose name has a capital second letter, such aseMails, is written whole, whichever capitalisation Lombok gives its clear method. So is one of an unbounded wildcard, or a GuavaImmutableTable. AnIterableproperty without type arguments is no longer taken for one. See A Lombok@Singularcollection. - A method that only starts like an accessor is never read as one (#1128). A name that runs on in lower case after its
get,isorsetprefix is a word, whilegeteMail()stays the JavaBeans getter ofeMail. Sogetaway()no longer readsaway, and a builder'ssettings(String)writessettings. See How a bean is read and written. - A word a component needs is refused, with the accessor to write (#1128). A bean's
getx()andsetx(int), with no accessor ofxbeside them, used to be read asx. The error now offersgetX()andsetX(int), or an@Unmappedmarker. See A method that only starts like an accessor. - A builder with both
name(...)andsetName(...)maps (#1128): the two were taken for rival overloads, which javac never weighs against each other, and the property was refused.name(...)writes it. See How a bean is read and written. - The processor names what an accessor-style leaf or marker meant (#907): a leaf written
getEmail()for the componentemailnow drawsDid you mean 'email()'?, as a near misspelling already did. An@Unmappedor@ReadOnlymarker named after its accessor,setStamp()orgetId(), draws the property the same way. See Compiler Messages. - A date codec refuses a formatter that cannot parse what it writes (#1144):
StandardCodecs.localDate(formatter)andoffsetDateTime(formatter)throwIllegalArgumentExceptionwhen built over one, such as a pattern with no year. Such a codec used to reject every input while quoting a sample it rejected itself. See Canonical forms only. - A misspelt
@MapKeyvalue suggests the component's bare name (#1112). The hint now offersbyOwnerrather thanbyOwner(), so it can be used as the annotation's value. Hints that name methods keep their parentheses.
Optics
Navigators by default
@GenerateFocusgenerates navigators by default, and a navigator is a path (#1033).UserFocus.address().street()works on a bare annotation, andgenerateNavigators = falseturns navigators off. A navigator can be held as aFocusPathor passed to an Effect Path'sfocus, and every path method works on it. See Fluent navigation with generated navigators.OrderFocus.lines().quantity()chains with no.via(...)(#1103). AList,Set,Collection,OptionalorMaybeof records gets a navigator, and so does one nested in another, such asList<Optional<LineItem>>, or aKindthe processor traverses. The navigator is the path the method returned before, so code that declares that path type still compiles. See Which fields get a navigator.OrderHistoryFocus.orders().customer().email()keeps navigating after a hop that widens the path (#1214). Each navigator nests a form for each wider path type,AffineFormandTraversalForm, and a widening hop into a record field returns it. A record that writesgenerateNavigators = trueis told when a form is left out. See Which fields get a navigator.- A navigator steps into a container only where the record widens it (#1033). A
Map, an array or a third-party collection of@GenerateFocusrecords gets a navigator underwidenCollections = true, or when the newwidenFieldsnames the field. So turning navigators on never changes what a field's method focuses on. See TheZERO_OR_MOREAsymmetry. - A navigator reaches into a generic record (#739, #1061): a field such as
Revision<EmailAddress> emailgets a navigator instantiated with its type arguments, soContactHistoryFocus.email().current()chains. Where a field gets no navigator, as one given a wildcard does, its note names the path its method returns. See A target with type parameters. - A field named like a path method is handled at the declaration (#1033). A navigator never reaches a field such as
last, sincename().last()would callFocusPath.last(). The field reaching such a record keeps its plain path, with a note, or is refused undergenerateNavigators = true. Fields namedget,setandcountnavigate. See A field named like a path method. - Fields such as
itemsandItemskeep one navigator between them (#1259). Where both would have a navigator, their classes would share a name, so the first in declaration order keeps it. The later one keeps its plain path, with a note, or is refused where the record writesgenerateNavigators = true. See Two fields with one navigator name. @GenerateFocuschecks the names inincludeFields,excludeFieldsandwidenFields(#1104). A name that matches no component is an error at the value, offering the component meant, such aspositionsfor"postions". A list the record never reads warns, as does awidenFieldsname whose field cannot widen. See Compiler Messages.- A
@GenerateFocussetting that names a constant javac cannot resolve leaves only javac's error (#1104), such asincludeFields = Names.MISSING. The processor stopped with an exception, which hid that error. @GenerateFocuswarns of anincludeFieldsname whose field has no navigator (#1181, #1201, #1207). Naming only aStringfield, say, left every field without a navigator, and nothing said why. The warning says why and offers a fix: another field to name, a record to annotate, a container to widen, orincludeFieldsto remove, with navigators off. See Compiler Messages.@GenerateFocusreports aTraversableGeneratorpriority tie once (#1081): the warning lands on the component that reaches the tied type, however many navigators reach it. It used to repeat for each one. See Compiler Messages.
Optics and null
- Optics over a nullable field pass a nullness checker (#767, #1017): every optic and Focus path type, and
Kind, takes a nullable type, so the lens generated for a@Nullablecomponent checks under JSpecify. A bridge into an effect type can say what a null focus becomes, astoEitherPath(source, error)does. See A focus that may be null. nullable()keeps the focus type (#1017):FocusPath.nullable()andAffinePath.nullable()return the focus type without its null, so a chainedfocus(path.nullable(), error)or avarinfers it where it inferredObject. A call writtenpath.<String>nullable()still compiles. See.nullable(): Handle Null Values.- A Focus path passes over a null it would step into (#1062). A
@NullableList,Optional,Eitheror other container now widens through.nullable()first, so its path is empty when the container is null, where it threwNullPointerException. A navigator intoMap<String, @Nullable Address>reads the other elements and leaves a null one in place. See Where a@Nullablecounts. modifyAllEither,modifyAllValidatedandmodifyAllMaybekeep a null element their traversal passes over (#1065): overfilteredor a prism that leaves a null element alone,modifyAllValidatedthrew andmodifyAllMaybegaveNothing. Each now keeps the null, and still rebuilds nothing once a step fails. So doOpticOps' validating modifiers. SeemodifyAllEither: First Error Only.
The containers a Focus path widens
- A
Maybecomponent's Focus path reads and writes itsJustvalue (#757):@GenerateFocusnow widens aMaybefield, and one nested in another container such asList<Maybe<A>>, through.some(Affines.just()). It used theOptional-only.some(), so the first read or write threwClassCastException. See Supported Container Types. - A raw or wildcard
Maybe,SetorCollectioncomponent widens, as aListdoes (#1251).Set<? extends Widget>widens to aTraversalPathontoWidget. In 0.4.11 such aSetorCollectionwas rejected at the declaration, and such aMaybethrewClassCastExceptionon first use. See Supported Container Types. @GenerateFocuswidens an array, as the Focus pages say (#1062). UnderwidenCollections = true, aString[]or other array of a reference type is aTraversalPathover its elements, and an array of@GenerateFocusrecords gets a navigator over them. A primitive array stays a plain path. See Supported Container Types.- A Focus path through a custom container whose generator names no optic stops at the container (#1059), as if no generator supported it. It used to widen through the
Optional-only.some()or theList-only.each(), and threwClassCastExceptionon first use for other containers. The generator still serves@GenerateTraversalsand@ImportOptics. See Registering your own container type. - A Focus path's untyped shortcuts are deprecated for removal in 0.6.0 (#1105).
each(),some(),at(index)and the others compile on any focus, then throwClassCastExceptionon the wrong one. Each typed form, such aseach(EachInstances.listEach()), reads the element type from the path and does not compile on another focus. See Deprecated for removal in 0.6.0. EachIndexedis the one way to an indexed traversal from anEach(#1261):Each.eachWithIndex(), deprecated since 0.4.7, is removed. It let the caller choose the index type, so a wrong one compiled, then threwClassCastException.indexedTraversal()fixes the index type in its signature. See Using Indexed Traversal.
Composition and cost
- Any two of
Iso,Lens,Prism,AffineandTraversalcompose directly withandThen(#1021). AnIsofollowed by aTraversalnow returns aTraversal, and so does aTraversalfollowed by anAffineor anIso, with noasTraversal()first. The composition table is read from the overloads, so it always matches the library. See Composition Rules Table. - A read through a traversal no longer rebuilds what it reads (#1056).
Traversals.getAll, aTraversalPath's reads, the indexed reads andIxedInstances.getread each focus asasFold()does, so reading a list or map through an optic no longer builds a new one. A prism or affine that matches builds nothing it throws away. See What reads cost. - A fold's query stops at its answer (#1056).
preview,find,exists,allandisEmptyonFold,TraversalPathandIndexedFoldvisit no focus after the one that settles them, soexistsover a long list returns at its first match. A path fromtracedstill reads every focus, so its observer sees them all. See What reads cost. - A Focus path steps into a
Listin time proportional to the list (#1063). Its.each(EachInstances.listEach()), which every generatedListpath uses, now rebuilds the list once instead of copying it at every element. On 100,000 elements that took several seconds, and now takes about a millisecond.EachInstances.arrayEach()rebuilds an array the same way. See Traversal allocation. - A map written through an optic keeps its source's order (#1022): every optic that writes a
Map, fromTraversals.forMapValuestoAtInstances.mapAtandSetter.forMapValues, hands back a map in the source's iteration order. An updated key keeps its place and a new key goes at the end. See Persistent and Third-Party Maps. modifyWhenandbranchon aTraversalcall only the function their predicate picks (#1026). Each element is now tested first, so a function may assume the condition that guards it, and an expensive one runs only where it is needed.Lens.modifyWhen,modifyBranchandsetIftest first too. See Complete, Runnable Example.Setter.forListandSetter.forMapValuesgive the same result on every run (#1018): each time the effect frommodifyFruns, it builds its own list or map. So anIOgives an equal value on every run, and theListapplicative gives every combination of choices.Traversals.traverseMapValuesdoes the same for an empty map. See Sequencing Effects in Collections.
Copy strategies, Lombok and protobuf builders
@ViaBuilderrebuilds through Lombok's@SuperBuilder(toBuilder = true)(#1110). ItstoBuilder()hands back a builder typed with wildcards,PetBuilder<?, ?>, whose setter andbuild()the processor now follows as javac does. So does a hand-written builder handed back with wildcards, where a step returns one of its own type parameters. See Non-Standard Naming.- More
@ViaBuilderchains are checked at the spec (#1110). A spec overNode<?>whosetoBuilder()returns a type parameter ofNodebuilds. A builder bounded by more than one type is checked too, and a chain that cannot compile is refused at the spec rather than in the generated file. See the chain the lens rebuilds through. - A
@ViaBuilderlens replaces a collection its builder would add to (#1110). For a Lombok@Singular List<String> tags, or a protobuf-java message'saddAllTagsorputAllPoints, the generated lens first calls theclearTags()orclearPoints()written beside it, so the lens laws hold. That covers@Builderand@SuperBuilder, with or without a lower-casesetterPrefix. See Non-Standard Naming. - A
@Witherthat returns its class's own type parameter works on a source written with a wildcard (#1110):S withName(String)on aShape<S extends Shape<S>>rebuilds aShape<?>. See Copy Strategies. - A spec's diagnostics never offer a deprecated method (#1110). Following one failed a build under
-Werrorin the generated file. A lens named after a protobuf repeated or map field is offeredgetTagsList()orgetPointsMap(), never the deprecatedgetPoints(). See the missing accessor. - The processor suggests methods spelt the way each copy strategy calls them (#907). A getter written
nameagainst a bean'sgetName()now drawsDid you mean 'getName'?, andgetActivedraws'isActive'. A setter or wither is offered in the convention its strategy needs: a builder'ssetNameorname, a bean'ssetName,@Wither'swithName. See Compiler Messages. @ViaConstructorholds its order to the constructor (#906). An order that passes a value where the constructor's parameter names say another belongs, or that no constructor takes, is now refused at the spec method. LeaveparameterOrderout and it is read from those names. See@ViaConstructor: constructor-only value types.- A
@ViaConstructororder that leaves a value out says so (#1067). A written order that rebuilds through a shorter constructor now warns, naming the values longer constructors take.@SuppressWarnings("shorter-constructor")keeps quiet a constructor that works the value out on purpose. A variable-arity constructor is held to its parameter names too. See the warning's entry. - A
@ViaCopyAndSetlens keeps what itswithXsetter returns (#1108). AwithTitlesetter had its new value dropped. The lens now hands back what it returns where every method of that name the new value can be passed to returns the source type. One it cannot keep warns, unless the lens method is annotated@SuppressWarnings("dropped-result"). See@ViaCopyAndSet. @ImportOpticstakes no word for a wither (#1128). A name that runs on in lower case afterwithis a word, so awithdraw(long)besidegetDraw(), which gave adrawlens that added todrawrather than setting it, now gives none. Awith2D(int)besideget2D()no longer stops the processor. See Wither Classes to Lenses.- A spec lens that would read or rebuild unchecked through a raw type is refused at the spec method (#905). A raw accessor read as
List<String>, or a setter called through a raw builder, left an unchecked operation in generated code, which failed a build under-Xlint:unchecked -Werror. The error names the member at fault. See Compiler Errors.
Prisms and @InstanceOf
- A prism onto an inner class of a generic class compiles under
-Werror(#886): aDotdeclared insideclass Outlines<X>getsPrism<Outlines.Outline, Outlines<?>.Dot>, from@GeneratePrisms,@ImportOpticsand an@InstanceOfspec method. A generic inner class gets a prism too, where@InstanceOfrefused one. See Generating the Prisms and Parameterised Targets. @InstanceOfreports a source argument outside the target's bound at the spec method (#1082). AGauge<Y extends Number>reached from aNode<U>whoseUcould be anything used to fail in the generated file, where javac refusedGauge<U>. Where one bound would do, the message gives the declaration to write. See Parameterised Targets.@InstanceOfrefuses a test that could never match (#1091). AGrid<Y> extends Node<Y[]>reached from aNode<int[]>crashed the processor, and is now refused at the spec method. Where the test earns no source type, as aBox<?>is noNode<U>, the message says no focus would do and offers another class or a spec. See the message's entry.- A method named after a Java keyword takes a trailing underscore (#1056). An enum constant such as
NEWorTRUE, a sealed subtype namedDefault, an@EffectAlgebrapermit namedThrow, or an importedwithDefaultcrashed the processor. Their methods are nownew_(),true_(),default_()andthrow_(), and so are@GenerateErrorEnvelopefactories. See Prisms.
Effect Paths
Values that are never null
- A
Rightor aSuccessalways holds a value (#1065):Either.right,Try.success, their Path,EitherTandErrorContextfactories, andofon their monads, refuse null, asValidatedandEitherOrBothalready did. A step that succeeds with nothing to return givesUnit.INSTANCE, asVTask.execandPath.ioRunnablealready do. See Creating Instances. - A
Leftalways holds an error (#1077):Either.left,EitherT.left,raiseErroronEitherMonadandEitherTMonad, andMaybe.toEitherrefuse null, as every Effect Path has done since 0.4.11. An error with no detail to carry isUnit.INSTANCE. See Creating Instances. - A lazy computation always gives a value (#1078):
IO,VTask,VStream,State,Lazy, every other lazy type and their Paths refuse a null value. A value passed in throws at once, and a function that returns null fails the computation when it runs. OnlyIdkeeps a null, for the optics. See A Task Always Gives a Value. - Every
raiseErrorrefuses a null error (#1078):MonadError.raiseErrortakes an error that is never null, so a nullness checker flagsraiseError(null)on any instance.Maybe,Optional,MaybeTandOptionalT, whose failure carries no detail, takeUnit.INSTANCE. See The Solution:raiseErrorandhandleErrorWith. bracketOutcome's release sees its error as aMaybe(#1077): release takes anEither<Maybe<E>, B>. A typed failure arrives asLeft(Just(e)), and a failedonDefectasLeft(Nothing), where it wasLeft(null). A release lambda that never reads the error needs no change. See Outcome-aware structured concurrency.- The Path SPI answers with an
Optionalwhere it has nothing to give (#1078):PathProvider.monadError()andPathRegistry.unregisterreturn anOptional, asPathRegistry.getProviderandGenericPath.monadError()do, where they returned null. See PathProvider SPI Registration. - A deferred trampoline step names a supplier that returns null (#1153, #1161):
Trampoline.defer,TrampolinePath.deferandPath.trampolineDeferreportsupplier must not return nullwhen that step runs. The supplier stays lazy.
Removed after their deprecation
StateTis built from its state function alone (#445): its constructor,createandStateTKindHelper.stateTtake noMonad, andmapTtakes only the transformation, as on every other transformer. The record holds only that function, soequals,hashCodeandtoStringleave theMonadout. See Running StateT Computations.TryandTryPathfold failure first, asEitherdoes (#452): the success-firstfold, deprecated since 0.4.6, is removed, leavingfoldFailureFirst. A call tofoldfails to compile rather than running its two functions the wrong way round. See Handling Failures.- Each narrow on
StateTKindandKindValidatorhas one name (#1260):narrowK, deprecated in 0.4.5, andnarrowWithPattern, deprecated in 0.4.4, are removed.StateTKind.narrowchecks the witness at compile time, whichnarrowK'sKind<?, A>parameter did not.KindValidator.narrowHoldertakes whatnarrowWithPatterntook. @PathConfigand theEFFECTFULandACCUMULATINGcapabilities are removed (#1262): all three were deprecated in 0.4.11 because none did what it said. No processor read@PathConfig, and the two capabilities generated whatCHAINABLEandRECOVERABLEdo, so nothing generated changes. To rename a Path class, setsuffixon@PathSource. See Naming and placement.
Resources, cancellation and concurrency
- A
VStreamreleases its resources when it stops early or fails (#1024): closing reaches every finaliser upstream that the stream has started reading, through any operator, innermost first.takeWhile,zipWith,headOption,findand a failing terminal operation close what they leave unread, sobracket(...).map(f).headOption()releases at once.VStream.closeAfterFailurecloses a failed manual pull. See onFinalize: Lightweight Cleanup. VStreamReactivepasses cancellation across the bridge (#1024): atoPublishersubscription closes the rest of its stream when it cancels or the stream fails, once it has requested elements. Closing afromPublisherstream cancels its subscription, sotake(n)stops the publisher. See Reactive Interop: Flow.Publisher Bridge.- One
Resourcecan serve overlapping uses (#1019): eachusereleases exactly what it acquired, however theResourcewas composed. So aResourcebuilt once withmaporflatMapand used nested, or once per concurrent request, no longer releases one resource twice and leaks the other. See Using One Resource Many Times. bracket,guaranteeandwithResourcereport a failing cleanup as try-with-resources does (#1053): when a use fails and its cleanup throws too, the use's exception is reported with the cleanup's suppressed onto it. An exception fromclose()is reported, so a writer whose final flush fails no longer reports success. See The bracket Pattern.- A release that blocks finishes after a cancellation (#1057, #1073): it runs with the interrupt status cleared, then restored. That covers a
bracketOutcomerelease, a release after a use that caught a cancellation and returned, and eachSagacompensation. A blocking release, such as a refund, used to fail at once. See The bracket Pattern and Saga. - A
Scopecan run again, be retried, or run on several threads (#1052): each run of theVTaskfromjoin()opens its ownStructuredTaskScopewith a newJoiner. A second run used to see the first run's results, and a retry kept failing with the first attempt's error.VResultPath.allSucceedandfirstSuccessfollow. See Running a Scope Again. - Resilience patterns keep a task's exception and its cancellation (#1072):
Retry.retryTask,Bulkhead.protectand their Path forms, andCircuitBreaker.protectWithFallback, no longer wrap a checked exception, soretryOn(IOException.class)retries a task'sIOException. A cancelled task is not run again or counted by a circuit breaker, and a timeout passes the cancellation on. See Handling Exhausted Retries.
Streams, sagas and stack safety
- A
VStreamchunk is aNonEmptyList(#580):chunk,chunkWhileandmapChunked, andchunkonVStreamPathandVStreamContext, hand over aNonEmptyList, sohead(),reduceandmaxon a chunk always give a value.NonEmptyListgainsstream(), so a batch function such aschunk -> chunk.stream().map(f).toList()compiles unchanged. See Chunking Operations. - A large chunk size or concurrency on a short stream works (#1238, #1249):
chunk(Integer.MAX_VALUE), orparEvalMapwith that concurrency, ran out of memory before reading an element. Each batch list was sized to the argument, and now starts small and grows. The other chunked and parallel calls, and theirVStreamPathandVStreamContextforms, follow. See Chunking Operations. - A saga builder with no steps does not compile (#580):
SagaBuilder.start()returns aSagaBuilder.Start, which has the step methods but nobuild(). Each step returns aSagaBuilderwhosebuild()never fails. An empty builder threwIllegalStateException. Steps that come from data are held as aSagaSteps. See Steps From Data. TrampolineUtils.traverseListStackSafeis stack-safe under every applicative (#1018): it combines the effects as a balanced tree, so a million-element traversal underIOno longer overflows the stack. It builds the result in linear time, and aValidatedcollecting its errors in a list is no longer quadratic in the failures. See Core Components.@PathSourcepoints an effect algebra toFreePath(#1008): a Path over a witness that@EffectAlgebragenerates now draws a note at the witness. The algebra has aFunctorand noMonadto pass to the Path'sofandpure, so the note shows how to wrap its programs in aFreePath. See A witness another processor writes.
Spring
- A streamed
VStreamPathresponse closes its stream when it stops (#1024): a client disconnect, a timeout or a serialisation failure now runs the stream's finalisers. See VStreamPath: SSE Streaming on Virtual Threads. - An SSE stream from a declarative HTTP client closes when you stop early (#1024):
headOption(),find(...)ortake(n)on a stream fromHkjClientExchange.vstreamnow closes the response. There is no need to drain it first. See Streaming withVStreamPath. - An endpoint that sends no body is declared
Unit(#1065): a declarative client method returningEitherPath<E, Unit>,VTaskPath<Either<E, Unit>>orMaybePath<Unit>ignores the body. A controller'sUnitsuccess writes no body from every handler that writes one value. See Choosing a Return Type. hkj-springis built against Spring Boot 4.1.1 (#1177):hkj-spring-boot-autoconfigurepublishes Spring Boot's BOM as a Gradle platform. Gradle then raises the Spring Boot your application runs with, and each library it manages, to at least 4.1.1's versions; a newer requested version still wins. Spring's dependency-management plugin or a Maven BOM import keeps your version, which must be 4.1.0 or later.
Testing
- The lens and affine law harnesses check set-set in both orders (#1169, #1175). The two focus values may be given in either order, and an affine is checked on its present target. The harness is stricter: a fixture that broke set-set only in the order left untested now fails. See Optic Laws.
- Lens and affine law fixtures may include the current focus (#812).
assertLensLawsandassertAffineLawsrequire only that their two focus values differ from each other. A boolean lens can usefalse, true. See Optic Laws. assertAffineLawslaw-checks an affine that writes to an absent focus (#1023):Affines.some()and aLens.andThen(Prism)affine now pass. On the absent target it checks thatmodifychanges nothing, thatseteither changes nothing or writes a value that reads back, and set-set. AddassertSetNoOpWhenAbsentfor an affine that must leave it alone. See When the focus is absent.- The library's own type classes are law-checked more widely (#1151, #1203, #1208, #1212, #1218, #1220, #1222). The Applicative laws now run for
List,NonEmptyList,EitherOrBothandTrampoline, the MonadError laws forCompletableFuture, and the Bifunctor laws forConstover generated values. The build now fails for aMonadwith no test class of its own. - Showcase tests demonstrate
EitherOrBothAssertandNonEmptyListAssert(#1191): they join thehkj-testexamples a reader can copy. Property tests inhkj-coreandhkj-checkershare one set of defaults (#1217).
Build and tooling
- Checker severity settings and the library's formatting work under every default locale (#1168):
severity=WARNINGand per-checkseverity:<id>=WARNINGnow select warnings under Turkish, where they were silently ignored. Library formatting and generated identifier case conversions useLocale.ROOT. Thehkj-coreandhkj-examplestests run under an English locale, withLocale.ROOTin more of their formatting (#1160, #1253, #1256). MigrateDeprecationsTo0_5_0dropsStateT'sMonadand widens navigated containers (#445, #1106):RemoveStateTMonadArgumentdrops theMonadfromStateTconstructor,create,stateTandmapTcalls.AddWidenFieldsForNavigatedContainersRecipeadds each container a 0.4.x navigator stepped into towidenFields, and marks one whose elements have a field named like a path method. See 0.5.0 deprecation migration.MigrateDeprecationsTo0_5_0marks the nulls to replace (#1065, #1077, #1078).DetectNullSuccessValuesRecipeandDetectNullErrorValuesRecipemark anullgiven as a success, a value or an error, and a lambda that returns one where it now fails. They also markVoidas a value or error type. They rewrite nothing, andrewriteRunleaves each mark as a comment. See 0.5.0 deprecation migration.MigrateDeprecationsTo0_6_0replaces a Focus path's untyped shortcuts (#1105): the new group'sReplaceUntypedPathShortcutsReciperewrites each call to its typed form, such aseach()toeach(EachInstances.listEach()). It drops a needless type witness, and marks each call it cannot rewrite, such as one over aList<? extends T>, with its fix. See 0.6.0 deprecation migration.- A nullness checker sees every
hkj-corepackage as@NullMarked(#1078):vstream,constant,func,writer_t,effect.spi,util.validationand the remaining optics packages carry the package annotation the others already did. NullAway readinghkj-corefrom the classpath ignores the module's annotation, so it treated those types as unannotated. - A nullness checker reads
hkj-test's assertions (#1094): its packages carry@NullMarked, which NullAway reading the jar from the classpath needs. EveryassertThat...entry point takes a@Nullableactual, as AssertJ's does, soassertThatEither(result).isNull()passes for a nullresult, where it threwNullPointerException. - Some
hkj-testassertions take or give a null (#1094):withMessage,withExceptionMessage,whenForcedThrowsWithMessageandIdAssert.hasValuetake a@Nullableexpected value.getException()andgetValue()onIOAssertandVTaskAssertreturn@Nullable, since an outcome has only one of the two. - A generator extending the built-in
Optional,Maybe,List,SetorCollectiongenerator inherits no optic expression (#757):@GenerateFocusrecognises those types by name and never asks a generator. SogenerateOpticExpression()andgetRequiredImports()on one return the defaults, and a subclass claiming another type takes no part in@GenerateFocusuntil it names its own. See Registering your own container type. - The
state-t-mapt-aritycheck is retired (#445): everymapTnow takes the function alone, so there is no missing argument to report. Adisable=orseverity:entry that names it has no effect. Thekind-value-narrowmessage now names the right accessor:run()onReaderTandWriterT, andrunStateT(state)onStateT. See Compile-Time Checks. hkj-processor-pluginsno longer brings Guava and Jackson 2 onto your annotation processor path (#1177): they came withpalantir-java-format, which it declared but never used. The plugin guide's build snippet now names javapoet and the right Avaje SPI artefact. See Step-by-Step Example.
Documentation
The Optics chapter
- The Optics chapter reads in order, from a first nested update to a PATCH (#1034, #1038, #1056, #1086). The sidebar starts with the pages a newcomer needs, then groups the rest by task. The examples follow one order service, the Mapping capstone's records, and every page keeps its address. See Optics.
- Find your field, then find your recipe (#1035, #1045). The Focus DSL page answers "what does my field give me?" with a table keyed by the field you hold. The Optics Cookbook opens on a "Find your recipe" table, each recipe leading with a Focus path. See Find your field and Find your recipe.
- The first lane ends in a capstone and a self-check (#1036, #1039, #1041). What a Path Is Made Of, Updates That Can Fail and Many Edits at Once are rewritten. Six pages pause for checkpoints the build proves, a capstone builds an order desk, and two new tutorials practise them. See Check Your Understanding.
- Two lookup pages translate what you already write into optics (#1046). Coming from Lombok, Streams and Switch sets each wither, stream and
switchhabit beside its path, and says when plain Java wins. Coming from Monocle or Haskell maps their names, and the six behaviours that differ. See Look It Up. - Compositions read as Java, and two hard rules come with a story (#1037, #1040). The chapter and the
hkj-opticsskill writeLens.andThen(Prism) = Affineinstead of>>>. Composition Rules opens on a short dialogue, and the Lenses page tells how a constructor that lowercases an email quietly breaks a lens. See Optic Composition Rules. - Every diagram draws each optic in one shape (#1044, #1049, #1055). A legend on the introduction gives every optic type its own shape, and every diagram fits a phone's screen and describes itself to a screen reader. Each deeper page opens with what you will be able to do (#1042). See How the optic types relate.
- Production Readiness opens on ten questions and holds the cost facts (#1043, #1050). The questions run from what a lens update allocates to whether generated classes stay stable across versions. Each optic's allocations and reads are stated in one place, naming the method behind each fact. See Production Readiness.
- Every example is compiled, and every name keeps one shape (#1020, #1047, #1048, #1101, #1114, #1119, #1120). A test holds each value the Optics pages state. A type name such as
Ordermeans one thing across the Optics and Mapping chapters, the skills and the annotations' javadoc. See Affines.
Glossary and reference pages
- The Effect Paths glossary gains nine entries (#1143, #1237, #1245, #1264):
CompletableFuturePath,EitherOrBothPath,EitherPath,IdPath,IOPath,LazyPath,MaybePath,OptionalPathandVTaskPath. Each gives a definition and its factories onPath, and four list their main operations. See Effect Paths. - The glossary gains entries across type classes, optics and concurrency (#1037, #1184, #1226, #1227, #1235). The type classes gain
Alternative,Foldable,MonadZeroandTraverse, the opticsGetter,IxedandEach, and the core effect typesReader,StateandWriter. Concurrency gainsRetryandSaga, and the optics listandThen,modifyFand path widening. See Type Classes. - Glossary entries name methods their types have (#1155, #1225):
getOptionalandbuildfor aPrism,getAllandpreviewfor aFold, andasFold().getAll(source)for aTraversal. The check that compiles the book's examples now counts every error, where it skipped any that mentioned "preview". See Prism. - The cheat sheet and the Type Conversions tables match the code (#1142, #1170, #1171, #1221). The Path Types table gains
FreeApPathandVStreamPath, and names the methods that extract aLazyPath, aGenericPathand aWithStatePath. Two conversion rows give the functiontoTryPathandtoIdPathtake. See Path Types. - The Capability Interfaces page matches the interfaces (#1182):
OptionalPathis onlyChainableand falls back withorElsePath, Combinable listszipWith, and Effectful namesguarantee. See Capability Interfaces. WriterTjoins the transformer overview (#1195): the overview, its Path table and Supported Types now list all six transformers. See WriterT.- The
hkj-testpage lists every assertion class (#1193):EitherOrBothAssert,NonEmptyListAssert,FieldErrorAssertand the Kind-narrowing wrappers join its table, in the book and in the module's README. See What Is Covered. StandardCodecs.locale()says a variant keeps its case (#1202):sl-rozajandsl-ROZAJboth round-trip, as two different locales, while a case-folded language, region or private-use subtag is still refused. See Standard codecs.- The contributing guide clones your own fork, and lists every module (#1194): its first steps now clone your fork, so the later push reaches it, and the module list includes
hkj-bomandhkj-test. See Contributing.
Other pages
@PathSourcehas a page of its own (#999): Custom Paths with@PathSourcesays when a generated Path is worth it overGenericPath, what each capability generates, and howerrorTypetypes recovery.- The Resource page gives the order finalisers run in (#1019): the order they were added, where it said the reverse. It also says which exception is reported when a release and a finaliser both throw. See Finaliser Behaviour.
modifyAllEithersays it keeps the first error, not that it stops there (#1031): every focused value is still validated, and the result keeps the first error in traversal order. The javadoc and the examples now say so too. SeemodifyAllEither: First Error Only.maxNavigatorDepthsays what it does (#1030). A value of1makes a navigator's own navigation methods return plain paths, and a larger value, the default included, ends no chain. Its javadoc and Tutorial 19 now say so. See Controlling navigator generation.- The Focus pages say what a wildcard inside a container's element gives you (#813). A
List<Box<? extends Widget>>widens to aTraversalPathoverBox<? extends Widget>, wildcard kept.BoxFocus.value()needs a named type argument, so the page shows the lens to write instead. See A wildcard inside the element. - Javadoc examples name monoids that exist (#1056):
Fold,IndexedFold,TraversalPath,ConstApplicativeand the Focus package overview showMonoids.integerAddition()and its siblings, where they namedMonoids.intSum()andMonoid.of. - Every chapter's Previous and Next links follow its contents (#1086, #1093). Ninety-five pages across eight chapters had stale or missing links, several skipping a page added since. The book check now finds every chapter itself, so a new one is held to its contents too.
Upgrading from 0.4.11
Many changes stop the build, most with a message that names the fix. Read Before you upgrade first, then What a running program can notice, which lists the changes the compiler cannot point at.
Before you upgrade
- Run
MigrateDeprecationsTo0_5_0before you move to 0.5.0 (#445, #452, #1260, #1262): takehkj-openrewrite0.5.0 for the run, and keep the project on 0.4.11 until it finishes. Its recipes find every removed API only on 0.4.x. Rename each element field itswidenFieldsrecipe (#1106) marks before you move, so the compiler finds the calls through it. See 0.5.0 deprecation migration. - Under
-Werror, runMigrateDeprecationsTo0_6_0in the same pass (#1105): on 0.5.0 a call to a deprecated Focus path shortcut fails a-Werrorbuild. Every typed form the recipe writes is in 0.4.11, so it can run besideMigrateDeprecationsTo0_5_0. Replace by hand each call it marks. - Replace
VoidwithUnitwhere a success has nothing to return (#1065, #1078):Either<E, Void>andTry<Void>can no longer hold their null. AVTask<Void>,IOPath<Void>or other lazy computation fails when its step returns null.VTask.exec,Path.vtaskExecandPath.ioRunnablegiveUnit. TheMigrateDeprecationsTo0_5_0run marks each site. - Recompile what was compiled against a generated Focus class (#1033, #1214): a field or a hop that gains a navigator returns a navigator class. A module compiled against the earlier class fails with
NoSuchMethodErroruntil it is rebuilt. - Recompile what calls
chunk,chunkWhile,mapChunkedorSagaBuilder.start()(#580):start()returns a new type, so a module compiled against 0.4.11 fails withNoSuchMethodError. A chunk keeps its erased type, so such a module links, then fails withClassCastExceptionwhere it reads a chunk as aList.
What a running program can notice
Mapping
- A date codec over a formatter that cannot parse what it writes throws when built (#1144):
StandardCodecs.localDate(formatter)andoffsetDateTime(formatter)throwIllegalArgumentException, with the formatter'sDateTimeExceptionas its cause. Such a codec used to be built, then refuse every input. - A bean property is read through its accessor, never a word declared before it (#1128): a
getaway()orisland()declared beforegetAway()orisLand()readawayorland, and the accessor now does. - A merge fill can parse through a generic spec in this compilation (#1123): a dependency's spec for exactly the pair used to fill it. It now resolves as a mapping's nest does, your own specs first. A leaf named after the component, delegating to the dependency's Impl, keeps the old fill.
Optics
- A
@GenerateFocusrecord field, or a container of one, returns a navigator (#1033, #1103, #1214): agetClass()check sees the navigator class, as it does the form a hop from a wider navigator returns. A navigator equals only a navigator of the same class over an equal path, never the plain path it wraps. - A navigator is a path (#1033): one from a record that wrote
generateNavigators = truepassesinstanceof FocusPath, and an overload taking aFocusPathis chosen over one takingObject. - A 0.4.x navigation call can compile against a path method instead (#1033, #1106, #1243): take a record that writes
generateNavigators = true, with aMapfieldbyTickerof records with alastfield.byTicker().last()then calls the path's ownlast(), which throwsClassCastException. A note at the field andMigrateDeprecationsTo0_5_0ask forlastto be renamed before you move. See Compiler Messages. - A null
@Nullablecontainer has no focus (#1062): a generated Focus path that steps into one reads nothing, and a modification leaves it null, where both threwNullPointerException. - A read through an
Optionalor aMaybereads a null focus as absent (#767):Fold.preview,TraversalPath.previewandtoMaybePath, andFocusPath.toMaybePathused to throwNullPointerException. So didFocusPath.asAffine,headOption,IxedInstances.getand the list opticslistAt,listHeadandlistLast, whose setters, and the list prisms'build, now write a null. - Other optics carry a null focus through (#767):
findpasses over a null focus to the next match, where it threw.Setter.forList, andFocusPaths.listElements,arrayElements,listCons,listSnoc,listTail,listInitandmapValues, carry a null element.IxedInstances.containscounts an index holding a null as present, andPlans.preflightwalks a batched traversal to the end, sotruncatedis nowfalse. - A traversal's Effect Path bridges leave a null focus out (#1017):
toStreamPath,toVStreamPathandVStreamPath.fromEachused to stream it, andtoListPathandtoNonDetPaththrewNullPointerException.TraversalPath.foldcounts it as the monoid's empty value, whereMonoids.string()appended"null".getAllstill keeps every focus. - A raw
modifyFunderEitherorTrycannot pass over a null element (#1065):filteredkeeps an element it passes over by lifting it withof, as do a prism and an affine that miss. Under those applicativesofnow refuses null.TraversalExtensions.modifyAllEitherand its siblings keep the element. OpticPrograms.getfails on a null focus, namingpreview(#1078): a Free program always gives a value, so the direct and logging interpreters throwNullPointerException, andValidationOpticInterpreterrecords an error.StateOps.viewon a null focus fails the same way.- A map an optic writes iterates in the source's order (#1022): it came back in hash order, so printed output or a test of a map's iteration order can change.
FocusPaths.mapValuesover an empty map returns a new map. - A
TreeMapkeeps its order but not its type (#1022):forMapValuesCollecting(TreeMap::new)keeps both when the keys sort in their natural order. ATreeMapwith its own comparator needs a collector that builds the newTreeMapwith that comparator. - The function a predicate does not pick is no longer called (#1026): by
modifyWhenandbranchon aTraversal, and bymodifyWhenandmodifyBranchon aLens.Lens.setIfno longer callsseton a failed predicate.Traversals.speculativeTraverseListis unchanged: it applies both functions to every element. - A query's predicate is not called after its answer (#1056):
find,existsandall, onFold,IndexedFoldandTraversalPath, stop at the focus that settles them, so a logging or counting predicate sees fewer calls. AFoldof your own whosefoldMapcatches every exception still gives the same answer, but walks on to its end. - A read no longer runs a traversal's rebuild (#1056): a
Traversalof your own whose rebuild logs or counts no longer runs it forTraversals.getAll, aTraversalPath's reads,IndexedTraversals.toIndexedList,getAllandlength, orasIndexedFold(). OptionalTraverse.traverserejects a function that returns null (#1056). For a present value, anullfrom the function used to give an emptyOptional. It now throwsNullPointerException, as the otherTraverseinstances do.- Sequenced lists are unmodifiable at every size (#1018):
IndexedTraversals.sequenceList,ForTraversal.toListandForIndexed.toIndexedListnow return one, asTraversals.traverseListdoes. So doIndexedTraversals.forListon a non-empty list andTraversals.traverseArrayon an empty array. Copy the result into a newArrayListto change it. ForTraversal.toListandForIndexed.toIndexedListhonour the filters (#1018): they collect only the elements that passfilterandfilterIndex, asmodifyandsetalready did. They used to return every focused element. See Collecting Results.- The list optics that returned an
ArrayListnow return an unmodifiable list (#1063):.each(),.at(i),.head()and.last()on a Focus path,ListTraversals,AtInstances.listAtandlistAtWithPadding, andIxedInstances.listIx. Changing one throwsUnsupportedOperationException, so copy it first. With nothing at the index,.at(i)andlistIxreturn your own list. See What each collection optic builds. - A Focus path over a field declared as
ArrayListfails when.each(),.at(i),.head()or.last()writes (#1063): storing the rebuilt list, an unmodifiableList, back throwsClassCastException..each()already failed on a one-element list. Declare the field asList, or useeach(EachInstances.fromIterableCollecting(ArrayList::new)). - A
@ViaConstructorlens with noparameterOrdersets through its constructor (#906): the order is read from the constructor's parameter names, wheresetused to throwUnsupportedOperationException. - A spec lens whose focus is wider than its getter's wrapper passes the value as it is (#906): with
Numberover anintgetter, it used to be cast tointforwithX(int). Where the wither, setter or constructor is also overloaded for a wider type, such aswithX(Object), that overload is now the one called. - A
@ViaCopyAndSetlens over awithXsetter hands back what it returns (#1108, #1128): not the copy, where every method of that name the new value can be passed to returns the source type. A fluentwithXbehaves as before, and so does everysetmethod. AwitheMail, as JavaBeans spells one foreMail, is awithXsetter too. - A
@ViaBuilderlens over a Lombok@Singularcollection, or through a protobuf-java message'saddAllTagsorputAllPoints, replaces the collection (#1110):setused to add the new elements to the onestoBuilder()copied. Code that set a one-element list to add an element now keeps only that element; append withmodifyinstead. - An
Eachof your own that declareseachWithIndex()without@Overrideis no longer indexed (#1261): it still compiles, as a method of its own, butsupportsIndexed()now returns false for it. ImplementEachIndexedinstead.
Effect Paths: null values
- A null success throws, or fails a
Try(#1065):Either.right(null),Try.success(null)and the Path factories throwNullPointerException, and so does anEitherorEitherPathmapwhose function returns null. InTry.of,map,ap,recoverandTryPath, a null result gives aFailureholding one. AVResultPathfails when it runs. - A null error throws (#1077):
Either.left(null),EitherT.left,raiseError(null)onEitherMonadandEitherTMonad, and amapLeftorbimapwhose error function returns null throwNullPointerException, where they builtLeft(null). raiseError(null)throws onMaybe,Optional,MaybeTandOptionalT(#1078): it gaveNothingor an emptyOptional. PassUnit.INSTANCE. EveryMonadErrornow refuses a null error.recover(ma, null)throws onEitherMonad,TryMonadandEitherTMonad(#1065): whether or notmafailed. A null fallback used to giveRight(null)orSuccess(null).Try.orElseGetrefuses a supplier that returns null, asValidated.orElseGetdoes;orElse(null)still hands a null back.- The conversions into an
Eithercheck their error even when it is not needed (#1077):Maybe.toEitherchecks its error, or its supplier, even on aJust. The optics helpersgetEither,getValidated,modifyEitherandmodifyValidatedcheck theirs even when the focus is there.Try.toEitherthrowsNullPointerExceptionfor a mapper that returns null, where it threwKindUnwrapException. - The one-argument
toEitherPathandtoTryPathon aFocusPaththrow on a null focus (#1065): they gaveRight(null)andSuccess(null). The message names the form that takes an error.EitherPath.focus(FocusPath)throws on a null focus too, namingfocus(path.nullable(), …), andTryPath.focusgives aFailure. ErrorContext.iotreats a null result as a failure (#1065): its error mapper sees aNullPointerException, where the context heldRight(null).- A function that returns null fails the computation (#1065, #1078): a callable, supplier,
mapfunction, combiner or recovery function that returns null fails the lazy computation when it runs, and theNullPointerExceptionnames that function. SoVTask.of(() -> { work(); return null; })fails, whererun()handed the null back. - A null result fails where a computation becomes a
Tryor anEither(#1065):runSafe,toTryPath,asTry,Scope.joinSafeandEffectBoundary.runSafegive aFailureholding aNullPointerException.joinEitherandFreePath.attemptgive aLeft, andcatchinghands it to its mapper. - A null value passed to a lazy factory throws (#1078):
VTask.succeed,State.pure,Reader.constant,Writer.value,Lazy.now,Trampoline.done,Free.pure,FreeAp.pure,VStream.of,Context.succeed, the Path factories such asPath.ioPureandPath.id, andofon their monads throwNullPointerException. So do the constructors ofStateTuple,Writer,Trampoline.Done,Free.PureandVStream'sEmitandSeed. StateT,ReaderTandWriterTrefuse a null value (#1078):ofon their monads,WriterT.ofandWriterT.writerthrow on one. A function given toReaderT.reader, or tomaporapon their monads, that returns null fails the computation.ReaderTMonad.of(null)overOptionalgave an emptyOptional.VTask.run()andrunAsync()refuse a null from a hand-written task (#1078): aVTaskwritten as a lambda that returns null throwsNullPointerExceptionfromrun(), and fails the future fromrunAsync().PathOps.firstVTaskSuccesscounts such a null as a failure and tries the next path.VTaskContext.runOrElserefuses a null default, andrunOrElseGeta handler that returns null.- An
IOPath,Parand aScoperefuse a null from a hand-writtenIOor task (#1078):IOPath.unsafeRun()throwsNullPointerExceptionfor one, as does each step that runs it, andEffectBoundary.embed.Par's combinators and aScopethat needs every subtask fail on such a null, and an accumulatingScoperecords it as an error. - A stream refuses a null element (#1078):
VStream.ofthrows on one, andfromList,fromIterator,fromStreamandgeneratefail the stream when they pull one.fromPublishercancels its subscription and fails the stream.StreamPathdoes the same for its sources.PathOps.traverseVStreamstill hands a null item in its list to the function. - A
CompletableFuturePathfails on a future that completes with null (#1078, #1072):Path.futureandfromFuturecheck the value as it arrives: map arunAsyncfuture toUnitfirst. Cancelling either future cancels both.withTimeoutandcompleteOnTimeoutcomplete only the new path's future, and the latter refuses a null default.CompletableFutureMonadkeeps the JDK's null. - An
IdPathalways holds a value (#1078):Path.id(null)throws, as doesPath.idPathgiven anIdholding null.FocusPath.toIdPaththrows on a null focus, namingtoMaybePath, and so doesIdPath.focus, namingfocus(path.nullable()).IdandIdMonadstill hold a null, since every optic'smodifyruns through them. - The lazy Paths'
focusrefuses a null focus (#1078):IOPath,VTaskPathandVStreamPathfail when they run, and the message names thepath.nullable()form to use. They used to hold the null. Context.askfails on a key bound to null (#1078): read a key that may hold null withasks.SecurityContext.requireAuthenticatedstill fails an anonymous user withUnauthenticatedException.- A null in a saga fails its step (#1065, #1078): a step whose task returns null fails like any other step, and so does a
mapthat returns null. The steps before it are compensated, underSaga.runandSaga.runSafealike. A null result gaveRight(null)fromrunSafe, andrunhanded it back. - A null result from a deferred trampoline step names its supplier (#1153, #1161). It is still a
NullPointerException, raised when the step runs: fromrun()forTrampoline.defer, and from the deferred step forTrampolinePath.deferandPath.trampolineDefer. No call site needs to change.
Effect Paths: resources, cancellation and streams
- A finaliser that used to be skipped now runs (#1024): when a
VStreamstops early, a terminal operation fails, or a function given to an operator such asmap,filterormapTaskthrows. A finaliser that throws then fails the operation that stopped, such astakeorheadOption. Closing runs the innermost finaliser first, as completing does. - A
bracketwhoseusefunction throws releases the resource (#1024): before the failure reaches the caller. It used to leave the resource acquired. - Closing a pulled
bracketstream releases its latest run (#1024): closing the head of abracketafter a pull releases the resource that pull acquired, where it used to release nothing. onFinalizeruns its finaliser for each consumption of aVStream(#1018): a second consumption, or one afterclose(), used to skip it. It now also runs when the source fails as it is pulled.close()runs it for the latest consumption only, unless that consumption has run it already. See onFinalize: Lightweight Cleanup.VStream.defercalls its supplier when the pulledVTaskruns (#1024): not whenpull()is called, and again on each run.generate,iterateandunfoldcall their functions then too.distinct,chunkandchunkWhilenow give the same step each time a pulledVTaskruns, and adistincttail pulled again replays its elements.VStreamPar.parEvalMap,parEvalMapUnorderedand theVStreamapplicative'sapread their input when the pulledVTaskruns (#1024): not whenpull()is called, and afresh on each run.VStreamPar.mergestarts a producer for each run of its pulledVTask(#1024, #1073): not whenpull()is called. Closing it stops that run's producer and waits for each unfinished source to close, even with an interrupt pending.close()now stops the producer when itsVTaskruns, not when it is called. See merge: Concurrent Multi-Source Consumption.- Cancelling a
toPublishersubscription runs the stream's finalisers (#1024): once the subscriber has requested elements. It used to leave the stream open. VStreamThrottle.throttlegives each consumption its own window (#1018): each consumption's first emission opens its first window. A later or concurrent consumption used to share the first one's window.Resource.onFailureruns its action when the use fails (#1019): it used to do nothing. The action runs before the release, both when the use fails and when a step composed after it fails while the resource is held, such asmap's function throwing. See onFailure Callback.Resource.mapreleases the resource when its function throws (#1019): it used to leave the resource acquired.- A
Resourcerejects a null value (#1019): anacquiregiven tomakeorfromAutoCloseablethat returns null, a null passed topure, and a function given tomapthat returns null now fail withNullPointerException. Hold an absent value as aMaybeinstead. See Factory Methods. - A failing use keeps its own exception when cleanup fails too (#1019, #1053): in a
Resource, and inIOPath.bracket,bracketIO,guarantee,guaranteeIOandVTaskPath.guarantee, the cleanup's exception is kept among the use's suppressed exceptions. When a release and a finaliser both throw, the release's exception is reported. See Exception Safety. and(second, third)releases its first resource when two releases throw one exception (#1019): the same exception instance from both used to fail withIllegalArgumentExceptionand leave the first resource acquired.- An exception from
close()failswithResource,withResourceIOandfromAutoCloseable(#1053): it used to be dropped. After a successful use,IOPaththrows it unchecked, aSQLExceptionas the cause of aRuntimeException, and the task fromResource.usefails. After a failed use, it is suppressed onto the use's. To ignore it, passbracketorResource.makea release that catches it. withResourceandwithResourceIOreject a null resource (#1053): a resource supplier that returns null now fails withNullPointerExceptionbefore the use runs. The use used to receive the null. Check for an absent resource before building the path.- Cleanup runs with the interrupt status cleared (#1019, #1053, #1057, #1073): after any use, in
Resource,IOPath,VTaskPath,VStreamfinalisers,bracketOutcomeand eachSagacompensation. So a blocking close is not cut short, and aScopethat timed out during the use waits for it. The status is restored afterwards, andwithResourcesets it whenclose()throws anInterruptedException. - A cancelled
bracketOutcomeuse leaves the thread interrupted (#1057): when the use throws anInterruptedException, the interrupt status is set again once the release has run. The thread's next blocking call then throwsInterruptedExceptionat once. To tell a cancellation from other failures, mapInterruptedExceptionto its own error inonDefect. - A task's checked exception passes through
Retry,BulkheadandprotectWithFallbackunwrapped (#1072):Bulkhead.protect,Retry.retryTaskand its fallback and recovery forms, andwithBulkheadandwithRetryonVTaskPathandVResultPath, wrapped it inVTaskExecutionException.CircuitBreaker.protectWithFallbackwrapped it, and anError, inRuntimeException. AretryOnpredicate, a fallback andRetryExhaustedException.getCause()now see the exception itself. - An interrupt stops a retry (#1072): an attempt that throws
InterruptedExceptionis not tried again, whatever the predicate, and the exception passes through as thrown, past any fallback. A thread left interrupted between attempts stops the retry with its status set andRetryExhaustedException, which a fallback still handles. A recovery task does not start after either. - A timeout passes a cancellation on (#1072): when a thread waiting on
VTask.timeoutis interrupted, so is the task, andInterruptedExceptionfollows once it stops or the time runs out. It used to leave the task running and report its result or aTimeoutException.withTimeoutonVTaskPath,IOPath,VResultPathandEitherPathbuilds on it. - A cancelled call trips no circuit breaker and skips a
ResilienceBuilderfallback (#1072): a call that throwsInterruptedExceptioncounts as neither a success nor a failure, whateverrecordFailuresays. The fallback lets it pass. VTask.run(),runSafe()andTry.attemptkeep a cancellation's interrupt status (#1072): each sets it again for anInterruptedExceptionit wraps or returns as the failure, and the Path forms built on them follow. So a retry aroundIOPath.withTimeoutsees the cancellation and stops, and the thread's next blocking call throws at once. The status used to be cleared.- An extremely long duration no longer throws (#1072): a duration too long to count in nanoseconds, such as
ChronoUnit.FOREVER.getDuration(), counts as about 292 years in timeouts, waits, retry delays, throttle windows andretryAfter. Some of those threwArithmeticException. Sub-millisecond parts are kept, whereRetryPolicyand several waits dropped them. Scope.firstCompleteandScopeJoiner.firstComplete()cancel the other subtasks once one completes (#1052): they used to wait for every subtask. Those still running are interrupted and those not yet started never start, so work a slower subtask does afterwards, such as writing a cache, no longer happens. Move work that must finish out of the race.ScopeJoiner.joiner()returns a newJoineron each call (#1052): code that opened a scope with one call and readresult()from another now reads aJoinerno scope has used. That gives an empty result or an exception, so use the value the scope'sjoin()returns.- A
Scope's timeout cancels its subtasks (#1052): when it expires, those still running are interrupted, and the run fails with aTimeoutExceptiononce they have stopped. So a subtask that ignores interrupts now delays the failure. Its subtasks now see the caller'sScopedValuebindings, which they used to find unbound. - An interrupt while a timed
Scopewaits fails the run (#1052): with anInterruptedException, as it does for an untimedScope. A timed one used to ignore it and keep waiting. ParandPathOpscopy the list they are given when called (#1052):Par.race,allandtraverse, andPathOps.sequenceVTask,traverseVTask,firstVTaskSuccess,parSequenceIOandraceIO. Changing the list afterwards no longer changes what a run does. A null task or path in a list, or inScope.forkAll's, is rejected then, not when it runs.Scope.namednames the scope in a JSON thread dump (#1052): the name used to be dropped.named(null)now throwsNullPointerException, where it was accepted.- A chunk is not a
List(#580): whatchunk,chunkWhileandmapChunkedhand over prints asNonEmptyList[1, 2]and never equals aList, even one holding the same elements. Comparechunk.toJavaList()where code checked a chunk against aList. - A large chunk size or concurrency on a short stream returns its elements (#1238, #1249):
chunk(Integer.MAX_VALUE)andparEvalMapwith that concurrency used to throwOutOfMemoryErrorbefore reading an element. The other chunked and parallel calls, and theirVStreamPathandVStreamContextforms, now return their elements too. A size that already returned chunks returns the same ones.
Effect Paths: lists and StateT
StateTequality leaves out theMonad(#445): twoStateTvalues wrapping the same function instance are equal, with equal hash codes, whicheverMonadbuilt them.toStringprints onlyrunStateTFn, so a test comparing the old text fails.- A list from
PathOpsorParcannot be changed (#1017): thesequenceandtraversemethods,Par.allandPar.traversehanded back their workingArrayList. So changing the list inside anEitherPathchanged what every laterrun()saw. Changing one now throwsUnsupportedOperationException, so copy it into a newArrayListfirst. TrampolineUtils.traverseListStackSafeandsequenceStackSafereturn an unmodifiable list (#1018): asTraversals.traverseListdoes. Changing it throwsUnsupportedOperationException, so copy it into a newArrayListfirst.
Spring
- The declarative HTTP client refuses an empty body for
EitherPathandVTaskPath(#1065): it gaveRight(null). It now throws anEmptyResponseBodyException, or fails the task, after the request has been made. A retry policy that retries every throwable would send it again, so exclude that type. DeclareUnitwhere the endpoint sends no body. - A
Unitsuccess writes no body (#1065): a handler that writes one value wroteUnitas the JSON string"INSTANCE". It now sends the resolved status with no body, as avoidcontroller method does. - A lazy Path whose step returns null writes the failure status (#1078): an
IOPath,VTaskPath,CompletableFuturePathorFreePathhandler wrotenullas the body. The step now fails, as a null does in any lazy computation. - A JSON
Eitherholding"right": nullor"left": nullis refused (#1065, #1077):EitherDeserializerreports it as bad input, asValidatedDeserializerdoes for a null value. - An SSE frame holding
nullfails the stream (#1078): a stream fromHkjClientExchange.vstreamfails withSseStreamException, as for any other frame it cannot read.
Testing
- The lens and affine law harnesses check set-set in both orders (#1169, #1175). A fixture that broke set-set only in the untested order now fails. A lawful lens or affine still passes with the values either way round.
- Lens and affine law harnesses accept one value equal to the current focus (#812). Existing lawful fixtures still pass, and two equal supplied values are still rejected. A setter that ignores updates still fails set-get on the other value.
assertAffineLawsno longer requiressetto leave an absent target alone (#1023): a test that relied on it to catch an affine writing there now passes. AddAffineLaws.assertSetNoOpWhenAbsent(affine, absentSource, value)to keep that check.
Build and tooling
- Uppercase warning settings take effect on Turkish systems (#1168): remove any workaround that lowercased
WARNINGbefore passing it to the checker. Generated for-comprehension lambda names now useirather than dotlessı; tuple constant names are unchanged. Numeric log counts use the root locale's digits. No source changes are required.
What stops a build that compiled
| Area | Now fails the build | Do this | Issue |
|---|---|---|---|
| Mapping | A @GenerateMerge fill whose pair two generic specs in this compilation both cover, where neither is more specific, which a dependency's spec for exactly the pair used to fill | Add the leaf the error spells, delegating to the spec you mean, or declare a spec for exactly the pair in this compilation | #1123 |
| Mapping | A bean with a method that only starts like an accessor, such as getx() and setx(int) for x, where nothing else reads or writes the component in its place: getaway() beside setAway(String) alone, or getid() read through a @ReadOnly marker | Rename it as the error offers, such as getX() or getId(). Where it is not meant to carry the component, name its property in an @Unmapped marker, where the error offers one | #1128 |
| Mapping | A PATCH bean's setter with no getter spelt seteMail(String), as JavaBeans spells one for eMail, or set_links(String) | Remove the setter, or rename it after the component it should update and give it a getter, or name its property in an @Unmapped marker | #1128 |
| Optics | Under a nullness checker, FocusPaths.nullable() assigned to an Affine<X, X> | Declare Affine<@Nullable X, X>, the type Affines.nullable() returns | #767 |
| Optics | Under a nullness checker, AtInstances.listAtWithPadding(null) for a non-null element type | Pad with a value, or name a nullable element type: AtInstances.<@Nullable String>listAtWithPadding(null) | #767 |
| Optics | nullable() whose result is bound to another focus type, as in AffinePath<S, Integer> x = stringPath.nullable(), which compiled as an unchecked cast | Convert the focus with a Getter or an Iso composed after nullable() | #1017 |
| Optics | Code that holds or chains the Focus path of a field reaching a container whose TraversableGenerator names no optic expression, as the AffinePath or TraversalPath it widened to: its declared type, a call such as getAll or via, or a navigator method through it | Use the path the Focus method now returns, which stops at the container, and widen it with .some(affine) or .each(each); or override generateOpticExpression() in the generator | #1059 |
| Optics | A prism onto an inner class of a generic class, used at its raw type: in a type argument, as in Prism<Outlines.Outline, Outlines.Dot> p = OutlinePrisms.dot(), or read back as a particular Outlines<String>.Dot | Write Outlines<?>.Dot or use var; where the value must be an Outlines<String>.Dot, cast to it under @SuppressWarnings("unchecked"), which states the unchecked step | #886 |
| Optics | An @InstanceOf target that is an inner class of a generic class, where the source does not pin an enclosing argument that the focus names, such as Outer<String>.Plain from a Node<String>, or that bounds a pinned argument, as Z extends X does, either of which compiled as an unchecked narrowing | Declare the focus with a wildcard there, Outer<?>.Plain, or narrow with @MatchWhen | #886 |
| Optics | var x = iso.andThen(traversal); (or traversal.andThen(affine) or traversal.andThen(iso)) followed by assigning another kind of optic to x; your own use(Optic) and use(Traversal) overloads now pick the Traversal one | Declare x as the Optic you mean to hold, or call the overload you mean | #1021 |
| Optics | A @ViaConstructor parameterOrder that passes a value where the constructor's parameter names say another belongs, such as {"y", "x"} for Point(int x, int y), including a lens named after an accessor no parameter is named after | Write the order the error offers, which follows those names, or rebuild with the strategy it names | #906 |
| Optics | A @ViaConstructor lens with no parameterOrder, whose set threw, where the constructors' parameter names give no order the call rebuilds through, a longer constructor might hold more, the lens's focus fits no constructor, or the source type is abstract | Write the order, declare the focus the error names, or rebuild with the strategy it names | #906 |
| Optics | A spec lens whose focus is wider than its getter's wrapper, such as Number over an int getter, rebuilding through an overloaded wither, setter or constructor none of whose overloads takes the focus; setting a value of another kind used to throw ClassCastException | Declare the focus as the wrapper, Integer | #906 |
| Optics | Under -Werror, a written @ViaConstructor parameterOrder that passes nothing for a value a longer constructor, or a record component, takes, such as {"amount"} beside Fee(long amount, String currency) | Write the order the warning offers, or annotate the lens method @SuppressWarnings("shorter-constructor") where the shorter constructor works the value out | #1067 |
| Optics | A @ViaConstructor parameterOrder that passes a value where a variable-arity constructor's parameter names say another belongs, such as {"note", "label"} for Sticker(String label, String note, String... parts) | Write the order the error offers | #1067 |
| Optics | Under -Werror, a @ViaCopyAndSet setter spelt as a wither whose result the lens cannot keep, such as Shrub withTitle(String) on a Hedge extends Shrub, or a fluent withX returning a raw or base type | Annotate the lens method @SuppressWarnings("dropped-result") where the method sets the copy it is called on; otherwise follow the warning's fix | #1108 |
| Optics | A spec lens whose generated code made an unchecked conversion or call, which compiled, with javac's note on unchecked operations, unless -Xlint:unchecked and -Werror were both on: an accessor that reads a raw type for a focus with type arguments, a focus written raw for a method or constructor that takes one with them, a raw accessor passed to a @ViaConstructor parameter that has them, a method called through a raw builder or a raw supertype, or a focus written raw for a method that takes the type a wildcard of the source or its builder stands for | Read and rebuild through members that keep their type arguments, or declare the lens over the raw type with @SuppressWarnings("rawtypes") on the lens method, or declare the spec with a type argument in place of the wildcard, or point toBuilder at a method that hands back the builder with its type arguments, or rebuild with another strategy | #905, #1110 |
| Optics | With widenCollections = true, a Focus method over an array of a reference type, or over a container holding one, such as String[] tags or List<String[]> rows, used as its old path or chained with .each(EachInstances.arrayEach()). A String @Nullable [] was an AffinePath to the array | Drop the .each(EachInstances.arrayEach()) and use the path over the elements, or keep the array's own path from its lens, FocusPath.of(SLenses.tags()), adding @GenerateLenses if the record lacks it | #1062 |
| Optics | With widenCollections = true, an array whose element is an SPI container, such as an Either or a Map, with a wildcard argument or none, such as Either<?, Widget>[] or Either[] | Name the element's type arguments, as in Either<String, Widget>[], or drop @GenerateFocus and add or keep @GenerateLenses, as for an Either<?, Widget> component | #1062 |
| Optics | A record that writes generateNavigators = true and holds another @GenerateFocus record, or a List, Optional or other container of one, that has a field named like a no-argument path method, such as last, head, each, segments, toLens or toPath | Rename that field, or keep the field that navigates into it a plain path, as the error says | #1033, #1103 |
| Optics | A @GenerateFocus includeFields, excludeFields or widenFields name that matches no component of the record, which changed nothing, or, as the only name in includeFields, left every field without a navigator | Write the component's name, as the error offers, or take the name out | #1104 |
| Optics | Under -Werror, a @GenerateFocus field list that changes nothing: excludeFields beside a non-empty includeFields, includeFields or excludeFields with generateNavigators = false, widenFields beside widenCollections = true, or a widenFields name whose field is widened anyway or holds no container | Remove the list or the name, or the setting that leaves it unread, as the warning says | #1104 |
| Optics | Under -Werror, an includeFields name matching a component that has no navigator, such as a String, an unannotated record or an unwidened Map of annotated records, including wildcard targets and wildcard containers | Follow the warning's fix line: name a navigator field, annotate the target record, widen the container, or remove includeFields and turn navigators off | #1181, #1201 |
| Optics | With generateNavigators = true and no widenCollections, a Map or a third-party collection of @GenerateFocus records, such as ImmutableList<Position> positions, used as the navigator over its elements: .positions().ticker(), getAll, modifyAll, count, or assigned to a TraversalPath | Run MigrateDeprecationsTo0_5_0 (Before you upgrade), or add the field to widenFields, as the note at the field says, or set widenCollections = true | #1033, #1106 |
| Optics | A Focus method's result for a field of another @GenerateFocus record, or of a List, Optional or other container of one, with its type inferred: a var then assigned another path, such as lines = lines.filter(p), or a generic call such as Stream.of(OrderFocus.customer()).toList() assigned to a List<FocusPath<Order, Customer>>. A navigator's hop into one record from a wider path, such as orders().customer(), is inferred the same way | Name the path type: declare the variable, or give the call its type argument | #1033, #1103, #1214 |
| Optics | On a record that writes generateNavigators = true, two components that both reach @GenerateFocus records and whose names differ only in the case of their first letter, such as items and Items. A List, Optional or other container of one now gets a navigator too. Both navigator classes would be ItemsNavigator, so the processor refuses the later one. On the default, the later one keeps its plain path, with a note | Rename one of the components, or keep the later one a plain path, as the error's fix line says | #1103, #1259 |
| Optics | Code calling a lens @ImportOptics generated through a word, such as draw() through withdraw(long) beside getDraw(); a class whose only wither was such a word is now refused | Drop the call: withdraw never set draw. Where the class has a method that does set the field, name it with @Wither in a spec interface | #1128 |
| Optics | A call to a Focus path's each(), some(), at(index), atKey(key), traverseOver(traverse), head(), last(), tail(), init(), cons(), snoc(), headTail() or initLast() (a [removal] warning) | Run MigrateDeprecationsTo0_6_0, or use the typed form that Deprecated for removal in 0.6.0 names | #1105 |
| Optics | A call to Each.eachWithIndex(), deprecated since 0.4.7, or an @Override of it | Declare the instance as EachIndexed, the type EachInstances.listEach() returns, and call indexedTraversal(), which returns the traversal itself rather than an Optional. Where you hold an Each you cannot redeclare, match it with instanceof EachIndexed<?, S, A> indexed. For an Each of your own with an index, implement EachIndexed | #1261 |
| Effect Paths | A Monad passed to new StateT<>(fn, monad), StateT.create, StateTKindHelper.stateT or StateT.mapT | Run the recipe as Before you upgrade says, or drop the argument by hand | #445 |
| Effect Paths | A two-component StateT record pattern, or StateT::create or StateT::new as a BiFunction | Match the one component, or take a Function, by hand | #445 |
| Effect Paths | A StateT in var or a chained call over a witness with a type argument, such as EitherKind.Witness<E>, which now infers Object for it | Name the types, as in StateT.<S, EitherKind.Witness<E>, A>create(fn), or declare the variable | #445 |
| Effect Paths | evalStateT and execStateT without a Monad, deprecated since 0.4.6 | Pass the outer Monad<F>, the one Instances.stateT took, as the last argument | #445 |
| Effect Paths | StateT.monadF(), deprecated since 0.4.6 | Keep a reference to the outer Monad<F> you built the stack with, and use it in place of the call | #445 |
| Effect Paths | Try.fold or TryPath.fold, deprecated since 0.4.6 | Run the recipe as Before you upgrade says, or call foldFailureFirst with the two functions swapped | #452 |
| Effect Paths | StateTKind.narrowK, deprecated since 0.4.5, or KindValidator.narrowWithPattern, deprecated since 0.4.4 | Run the recipe as Before you upgrade says, or rename the call to narrow or narrowHolder. Declare an argument typed Kind<?, A> as the Kind<StateTKind.Witness<S, F>, A> that narrow takes | #1260 |
| Effect Paths | @PathConfig on a package, deprecated since 0.4.11, which now fails with cannot find symbol | Run the recipe as Before you upgrade says, or delete it and its import, since it changed nothing generated | #1262 |
| Effect Paths | EFFECTFUL or ACCUMULATING, deprecated since 0.4.11, wherever it is named, as in @PathSource or a switch over PathSource.Capability | Run the recipe as Before you upgrade says, or write CHAINABLE for EFFECTFUL and RECOVERABLE for ACCUMULATING, which generate the same class. In a switch that already lists the replacement, delete the case | #1262 |
| Effect Paths | A call to ScopeJoiner.resultEither() (a [removal] warning) | Use joinEither() on the Scope, as Deprecated for removal in 0.6.0 says | #1052 |
| Effect Paths | Under a nullness checker, a nullable value passed to Either.right, Try.success, Path.right, Path.success, TryKindHelper.success, EitherT.right, the Either.Right or Try.Success constructor, or of on EitherMonad, TryMonad or EitherTMonad | Pass Unit.INSTANCE for a step with nothing to return, or route a value that may be missing through Maybe | #1065 |
| Effect Paths | Under a nullness checker, a nullable error passed to Either.left, EitherT.left, the Either.Left constructor, or raiseError on EitherMonad or EitherTMonad | Pass an error that says what went wrong, or Unit.INSTANCE where the error type is Unit | #1077 |
| Effect Paths | A bracketOutcome release whose outcome parameter is declared Either<E, B>, or that reads the error as an E | Take Either<Maybe<E>, B>, and read the error through the Maybe | #1077 |
| Effect Paths | Under a nullness checker, a nullable value passed to a lazy value factory such as VTask.succeed, State.pure, Lazy.now or Path.ioPure, or to of on their monads or on a transformer's, or a hand-written VTask, IO or Reader lambda that returns one | Pass Unit.INSTANCE for a step with nothing to return, or route a value that may be missing through Maybe | #1078 |
| Effect Paths | Under a nullness checker, a nullable error passed to raiseError on any MonadError, such as MaybeMonad or OptionalMonad | Pass an error that says what went wrong, or Unit.INSTANCE where the error type is Unit | #1078 |
| Effect Paths | A PathProvider that overrides monadError(), or code that reads monadError() or PathRegistry.unregister as a value that may be null | Return Optional.of(...) from the override, and read the Optional | #1078 |
| Effect Paths | Under a nullness checker reading hkj-core from the classpath, a nullable argument to a type it treated as unannotated: VStream, Const, WriterT, the Path SPI or the validation helpers | Pass a value, or route one that may be missing through Maybe | #1078 |
| Effect Paths | Code that treats a chunk from chunk, chunkWhile or mapChunked as a List: a type such as VStream<List<A>> or List<List<A>>, a reference to a method taking List<A>, such as VStream::fromList, a List method such as get, getLast or isEmpty, or a stored Function<List<A>, List<B>> passed to mapChunked | Declare NonEmptyList<A>, and use head() and last() for get(0) and getLast(). Pass chunk.toJavaList() where a List is needed, add .map(NonEmptyList::toJavaList) to keep a VStream<List<A>>, or pass a stored function as fn.compose(NonEmptyList::toJavaList) | #580 |
| Effect Paths | A variable, var included, or a parameter that holds SagaBuilder.start(), such as one a loop adds steps to | Declare it a SagaSteps, the type both stages share, and switch on it to build, as Steps From Data shows | #580 |
| Effect Paths | build() on SagaBuilder.start() with no step added, which threw IllegalStateException | Add a step before build() | #580 |
| Spring | An @HkjHttpClient method whose success type is Void | Replace Void with org.higherkindedj.hkt.Unit | #1065 |
| Testing | EitherAssert.hasRightNull() | Delete the assertion: a Right can no longer hold null | #1065 |
| Testing | EitherAssert.hasLeftNull() | Delete the assertion: a Left can no longer hold null | #1077 |
| Testing | IOAssert.hasValueNull(), hasNullValue() on VTaskAssert, LazyAssert, StateAssert and WriterAssert, and ReaderAssert's producesNull() | Delete the assertion: a lazy computation can no longer give null | #1078 |
| Testing | Under a nullness checker, getException() or getValue() on IOAssert or VTaskAssert assigned to, or returned as, a non-null type | Declare the result @Nullable and check it, or assert on the outcome instead, as hasValue and throwsException do | #1094 |
Deprecated for removal in 0.6.0
The MigrateDeprecationsTo0_6_0 recipe runs each recipe the table names. See 0.6.0 deprecation migration.
| Deprecated | Replacement | Recipe |
|---|---|---|
A Focus path's each(), on FocusPath, AffinePath, TraversalPath and a navigator | each(EachInstances.listEach()) | ReplaceUntypedPathShortcutsRecipe |
A Focus path's some() | some(FocusPaths.optionalSome()) | ReplaceUntypedPathShortcutsRecipe |
A Focus path's at(index) | via(FocusPaths.listAt(index)) | ReplaceUntypedPathShortcutsRecipe |
A Focus path's atKey(key) | via(FocusPaths.mapAt(key)) | ReplaceUntypedPathShortcutsRecipe |
A Focus path's traverseOver(traverse) | each(EachInstances.fromTraverse(traverse)) | ReplaceUntypedPathShortcutsRecipe |
A Focus path's head(), last(), tail() and init() | via(FocusPaths.listHead()), via(FocusPaths.listLast()), via(FocusPaths.listTail()) and via(FocusPaths.listInit()) | ReplaceUntypedPathShortcutsRecipe |
A Focus path's cons() and headTail() | via(FocusPaths.listCons()) and via(FocusPaths.listHeadTail()) | ReplaceUntypedPathShortcutsRecipe |
A Focus path's snoc() and initLast() | via(FocusPaths.listSnoc()) and via(FocusPaths.listInitLast()) | ReplaceUntypedPathShortcutsRecipe |
ScopeJoiner.resultEither() | 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 |
Previous: Unreleased: 0.5.1 Next: v0.4.11