You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Is there a way to break this into 2 parts - the pure api and the implementation?
Downstream frameworks (like Spring & Quarkus) may want to use their own Jackson versions & their own http implementations. Using the client as-is does not allow for this customization.
I maintain the Docling Java client and we have successfully done that - separated into 2 artifacts, the api & the client. Downstream frameworks like Spring & Quarkus only depend on the API and provide their own implementations of it. See https://github.com/docling-project/docling-java/tree/main/docling-serve
The Docling api also supports both Jackson 2 & 3 simultaneously.
We use a ServiceLoader abstraction, so you never actually have to use the implementation imports directly. Everything is driven through the API.
Apps that use Jackson 3 are unable to use this api at all.
I'm happy to help contribute if there is interest.
@edeandrea I'm not sure I fully understand what the advantage is. What would be unlocked by such a separation?
Additionally, our APIs are currently auto-generated using fern, essentially an OpenAPI spec. If there is a stable way to do this, we'd be happy to explore.
The async operations force CompletableFuture (the concrete implementation) rather than CompletionStage (the definition).
I wanted to use this API, but unfortunately I can't in its current state, as the dependency on OkHttp is non-starter. I've had to take the raw OpenAPI spec and use openapi-generator to generate my own implementation. Forcing CompletableFuture on me is also a non-starter.
Other frameworks (like Spring & Quarkus) don't use OkHttp. Spring 4 is based on Jackson 3, not Jackson 2. They also have their own async frameworks (Project reactor for Spring, Mutiny for Quarkus).
The separation would allow for the "models" and an API interface to be part of the API, and then an implementation module which could have a default implementation (OkHttp, Jackson, etc). Downstream frameworks (such as Spring/Quarkus) could use the api and define their own implementations using the relevant technologies from their ecosystems (in Spring - RestClient, in Quarkus - Jakarta Rest Client).
I ran into this exact same problem when building the Docling Java API. We've done it in a way where the API (the API interface & corresponding model pojos) are defined in an API module, and the reference implementation (based on the built-in Java HttpClient) are in a client module. The API module supports both Jackson 2 & 3 simultaneously.
Note
If you aren't aware, Jackson 2 -> 3 has many breaking changes, including the Java package name structure.
Because the api is just an interface, it can be re-implemented using whatever tech stack the client wants.
Same with the async api. By forcing CompletableFuture you are forcing a concurrency implementation on the end user, rather than allowing the end user to adapt to something that implements CompletionStage (Mono/Flux for Spring, Uni/Multi for Quarkus).
our APIs are currently auto-generated using fern
I'm not familiar with fern honestly, so I can't offer any insight. I'm sure the separation will require some thought and proper design - something I went through late last year with Docling (docling-project/docling-java#2 & docling-project/docling-java#141).
I'm open to helping/contributing if this is an initiative you'd like to take on.
@edeandrea We would definitively appreciate any assistance in improving this package. For us the main requirement will be that most of the package can be auto-generated to keep maintenance effort on new API routes etc minimal.
Thank you a lot for the added context! This really puts it into perspective and makes a lot of sense.
We would definitively appreciate any assistance in improving this package
Yay! I'd love to help!
For us the main requirement will be that most of the package can be auto-generated to keep maintenance effort on new API routes etc minimal.
As I mentioned, I'm not familiar with fern. I haven't looked at things in depth, but my initial thinking is that we could use something like OpenAPI generator to generate the models. We may need to build a custom template to generate the api interface (without implementation) and to be able to support both Jackson 2 and 3 simultaneously. I'd have to dig a little deeper.
Then in the client module you could again use openapi generator to generate just the api implementation. The model classes would be in the api. Again, we may need to build a custom template to make the generated api implement the interface in the api.
You'll probably have to hand-code the ServiceLoader spi, but thats not a big deal and is easily maintainable.
With Docling we decided not to generate the models from the spec, but to hand code things. It was a deliberate decision, mostly based on the fact that docling-serve doesn't do true api-first development - they generate the OpenAPI spec from the FastAPI runtime. If you're doing "true" contract-first, then generating might work better. We were also under a time crunch and it was simply faster to hand-code the models rather than invest in custom openapi-generator templates since the api is relatively small.
@Steffen911 Would it be possible to describe the process you use to generate this api currently?
I've read https://github.com/langfuse/langfuse-java#updating but it seems there are some manual steps in there. Would it be possible to provide a step-by-step set of instructions on how to generate the api?
I really like @edeandrea's proposal and PR, which I hope will be merged soon. It will enable more integration possibilities for Langfuse in the Java ecosystem.
Activity
edeandrea commentedon May 7, 2026
Same with the async operations. They should return
CompletionStage(the API) instead ofCompletableFuture(the implementation)Steffen911 commentedon May 19, 2026
@edeandrea I'm not sure I fully understand what the advantage is. What would be unlocked by such a separation?
Additionally, our APIs are currently auto-generated using
fern, essentially an OpenAPI spec. If there is a stable way to do this, we'd be happy to explore.edeandrea commentedon May 19, 2026
The current approach has 3 limitations:
CompletableFuture(the concrete implementation) rather thanCompletionStage(the definition).I wanted to use this API, but unfortunately I can't in its current state, as the dependency on OkHttp is non-starter. I've had to take the raw OpenAPI spec and use openapi-generator to generate my own implementation. Forcing
CompletableFutureon me is also a non-starter.Other frameworks (like Spring & Quarkus) don't use OkHttp. Spring 4 is based on Jackson 3, not Jackson 2. They also have their own async frameworks (Project reactor for Spring, Mutiny for Quarkus).
The separation would allow for the "models" and an API interface to be part of the API, and then an implementation module which could have a default implementation (OkHttp, Jackson, etc). Downstream frameworks (such as Spring/Quarkus) could use the api and define their own implementations using the relevant technologies from their ecosystems (in Spring -
RestClient, in Quarkus -Jakarta Rest Client).I ran into this exact same problem when building the Docling Java API. We've done it in a way where the API (the API interface & corresponding model pojos) are defined in an API module, and the reference implementation (based on the built-in Java HttpClient) are in a client module. The API module supports both Jackson 2 & 3 simultaneously.
Note
If you aren't aware, Jackson 2 -> 3 has many breaking changes, including the Java package name structure.
The separation I did in Docling is done using the Java
ServiceLoadermechanism - the API module exposes aServiceLoaderspi which the client module implements. This mechanism is what allows downstream frameworks to provide their own implementations (see https://github.com/quarkiverse/quarkus-docling/blob/main/runtime/src/main/resources/META-INF/services/ai.docling.serve.api.spi.DoclingServeApiBuilderFactory for the Quarkus implementation).Because the api is just an interface, it can be re-implemented using whatever tech stack the client wants.
Same with the async api. By forcing
CompletableFutureyou are forcing a concurrency implementation on the end user, rather than allowing the end user to adapt to something that implementsCompletionStage(Mono/Fluxfor Spring,Uni/Multifor Quarkus).I'm not familiar with fern honestly, so I can't offer any insight. I'm sure the separation will require some thought and proper design - something I went through late last year with Docling (docling-project/docling-java#2 & docling-project/docling-java#141).
I'm open to helping/contributing if this is an initiative you'd like to take on.
RyanDeleurme commentedon May 19, 2026
We also ran into a similar issue with this. Our company's OkHttp version is behind the Langfuse package, so we had to use two different apis:
Steffen911 commentedon May 20, 2026
@edeandrea We would definitively appreciate any assistance in improving this package. For us the main requirement will be that most of the package can be auto-generated to keep maintenance effort on new API routes etc minimal.
Thank you a lot for the added context! This really puts it into perspective and makes a lot of sense.
edeandrea commentedon May 20, 2026
Yay! I'd love to help!
As I mentioned, I'm not familiar with fern. I haven't looked at things in depth, but my initial thinking is that we could use something like OpenAPI generator to generate the models. We may need to build a custom template to generate the api interface (without implementation) and to be able to support both Jackson 2 and 3 simultaneously. I'd have to dig a little deeper.
Then in the client module you could again use openapi generator to generate just the api implementation. The model classes would be in the api. Again, we may need to build a custom template to make the generated api implement the interface in the api.
You'll probably have to hand-code the ServiceLoader spi, but thats not a big deal and is easily maintainable.
With Docling we decided not to generate the models from the spec, but to hand code things. It was a deliberate decision, mostly based on the fact that docling-serve doesn't do true api-first development - they generate the OpenAPI spec from the FastAPI runtime. If you're doing "true" contract-first, then generating might work better. We were also under a time crunch and it was simply faster to hand-code the models rather than invest in custom openapi-generator templates since the api is relatively small.
edeandrea commentedon May 26, 2026
@Steffen911 Would it be possible to describe the process you use to generate this api currently?
I've read https://github.com/langfuse/langfuse-java#updating but it seems there are some manual steps in there. Would it be possible to provide a step-by-step set of instructions on how to generate the api?
ThomasVitale commentedon Jun 3, 2026
I really like @edeandrea's proposal and PR, which I hope will be merged soon. It will enable more integration possibilities for Langfuse in the Java ecosystem.
Steffen911 commentedon Jun 6, 2026
@edeandrea I'm fine if we skip the fern-based client generation and move to something that takes https://github.com/langfuse/langfuse/blob/main/web/public/generated/api/openapi.yml as a baseline as long as this is well documented and easy to execute based on the README in this repository. Will take a look at the PR!
Thank you so much for all the effort in this 🙏
edeandrea commentedon Jun 6, 2026
That's exactly what I did. Everything here starts from the openapi document.
I did dd a follow-on issue that addresses some gaps I found in the openapi document.
edeandrea commentedon Aug 13, 2026
Hi @Steffen911 - any appetite for merging #36 ?