Series overview
Part 19 of 2095% complete
2026-04-29•15 min read

Java interoperability — when the JDK is already enough

A library that demands you rewrite your codebase to use it is a library nobody adopts. jfp is designed to live at the edges of a normal Spring Boot service — the interop package converts in both directions, and this chapter is the honest accounting of when each side is the right tool.

The converters

interop/Optionals.java
public static <T> Option<T> toOption(Optional<T> optional) {
return optional.map(Option::some).orElseGet(Option::none);
}
public static <T> Optional<T> toOptional(Option<T> option) {
return option.fold(Optional::empty, Optional::of);
}
interop/Streams.java
public static <T> Stream<T> stream(Option<T> option) {
return option.fold(Stream::empty, Stream::of);
}
public static <T> Option<T> headOption(Stream<T> stream) {
return Optionals.toOption(stream.findFirst());
}

The round trip is total: toOption(toOptional(o)) == o for every value, which means you can adopt jfp inside one service method and convert back at the boundary — incremental adoption without a flag day.

The decision table

JDK featureUse it whenThe jfp alternative teaches
Function<T, R>Standard unary functions are enoughFn1 keeps one vocabulary and carries the Kleisli helpers
Optional<T>A method may validly return nothingOption exposes fold and makes the laws testable — use Optional in public APIs
Stream<T>Processing sequences, filtering, collectingFoldable/traverse explain what collect is doing
CompletableFuture<T>Any async boundaryResult inside the future separates domain failure from execution failure
ExceptionsUnrecoverable, unexpected failuresResult/Validated model expected outcomes explicitly
Records + sealed interfacesImmutable data, finite alternativesThey are the ADTs — Option is Some | None, no magic

Two cells deserve emphasis. Optional is the right choice for a public method that may return nothing — Java developers know it, IDEs flag misuse, and jfp’s Option adds nothing a caller needs. And the JDK’s sealed interfaces already do the heavy lifting for sum types; the library types exist because they bundle the operations (map, flatMap, fold) with the alternatives, not because Java lacked the type.

The integration pattern

In a Spring Boot service the adoption path is narrow: repositories and clients keep returning Optional/CompletableFuture as usual; convert to Option/Result at the service’s inner edge where the functional pipeline begins; convert back (or fold into a response) at the controller. The functional core never leaks into framework annotations, and the framework never leaks null into the core. Part 20 wires exactly that boundary end to end.

JavaFunctional Programming

Type to search the site.

↑↓ navigate⏎ openPowered by Pagefind