Skip to content

feign-kotlin: add non-blocking WebClient AsyncClient and improve coroutine support - #3645

Open
ggoowlgns wants to merge 7 commits into
OpenFeign:masterfrom
ggoowlgns:combined-coroutine-support
Open

ggoowlgns wants to merge 7 commits into
OpenFeign:masterfrom
ggoowlgns:combined-coroutine-support

Conversation

@ggoowlgns

@ggoowlgns ggoowlgns commented Oct 25, 2026 •

Copy link
Copy Markdown

Summary

This PR combines coroutine support improvements for Feign and adds a Spring WebClient-backed non-blocking transport:

  1. Fix suspend return types on the blocking builder (feign-kotlin: Decoder type returns "Object" when method is suspending #2019) — add KotlinDelegatingContract, which resolves Kotlin suspend return types with kotlin-reflect instead of exposing the JVM continuation signature (Object) to decoders. Regression tests cover this path.
  2. Document non-blocking CoroutineFeign execution (How to use AsyncCient for non blocking requests? #2374) — explain that the default DefaultAsyncClient wraps a blocking client and show how to supply a non-blocking AsyncClient.
  3. Support suspend methods returning Flow<T> (Support sse client #971) — decode the HTTP body as a buffered List<T> and expose it as a cold Flow. This is not incremental HTTP/SSE streaming.
  4. Add feign-webflux — implement WebClientAsyncClient<C> as a Feign AsyncClient backed by Spring WebClient. It accepts a WebClient or WebClient.Builder, reusing configured connectors, filters, codecs and transport settings. With Reactor Netty, requests use its event-loop resources; a suspended caller does not park a thread while awaiting the response. The transport completes the future asynchronously, and response bodies are aggregated before Feign decoding.

Spring + CoroutineFeign usage

interface GitHubApi {
  @RequestLine("GET /users/{owner}/repos")
  suspend fun repositories(@Param("owner") owner: String): List<Repository>
}

@Configuration
class GitHubFeignConfiguration {
  @Bean
  fun gitHubApi(builder: WebClient.Builder): GitHubApi {
    @Suppress("UNCHECKED_CAST")
    return CoroutineFeign.builder<GitHubApi>()
      .client(WebClientAsyncClient<Any>(builder) as AsyncClient<GitHubApi>)
      .decoder(JacksonDecoder())
      .target(GitHubApi::class.java, "https://api.github.com")
  }
}

In Spring Boot WebFlux, the injected builder can carry the application's WebClient configuration. With the Reactor Netty connector, the application and client can share Reactor Netty's default event-loop resources. This adds manual Spring bean integration, not @FeignClient auto-configuration for spring-cloud-openfeign.

Behavior and compatibility notes

  • feign-webflux requires Java 17+ (Spring Framework 7 baseline). Adding this optional module does not change Feign Core's Java baseline.
  • A new integration test delays the HTTP response while using a single-thread caller dispatcher, then verifies another coroutine task runs on that dispatcher before the response arrives. It also verifies a filter configured on the supplied WebClient.Builder reaches the request.
  • The test demonstrates that the caller dispatcher is not blocked while suspended; it does not mean no thread is active at all. Reactor Netty event-loop threads still perform I/O and completion work.
  • Response bodies are buffered before decoding; neither this adapter nor Flow<T> provides true streaming.
  • Per-request Feign read timeout is applied as an overall reactive timeout that cancels the request. Feign per-request connect timeout and redirect options are not translated; configure these on the WebClient connector.
  • Decoding runs on the future completion thread, typically the Reactor Netty event loop. Keep decoders and completion callbacks short and non-blocking.
  • Default Reactor Netty resources are shared. The application is responsible for lifecycle/disposal of custom Netty resources or connectors it supplies.
  • Non-success HTTP statuses remain Feign Response values for ErrorDecoder handling.

Testing

  • Full required reactor: ./mvnw -pl webflux -am test -Dtoolchains.skip=true -Dtoolchain.skip=true -Dmaven.javadoc.skip=true -Dlicense.skip=true — 898 tests, 0 failures/errors, 4 skipped across core, Jackson, Java 11, Kotlin and WebFlux.
  • WebFlux module: 9 tests passed, including the delayed-response coroutine suspension test.
  • ./mvnw -pl webflux license:check -Dtoolchains.skip=true -Dtoolchain.skip=true -Dmaven.javadoc.skip=true — passed.

Fixes #2019
Fixes #2374
Closes #971 (buffered Flow semantics; true streaming left for discussion)

jihoon and others added 5 commits October 24, 2026 07:20
…builder

Resolve the declared Kotlin return type for suspend functions when the
plain Feign.builder() path is used, so decoders no longer receive
java.lang.Object. See issue OpenFeign#2019.
Cover both the blocking Feign.builder() path with
KotlinDelegatingContract and the CoroutineFeign path (OpenFeign#2019).
Adds a README section explaining how to combine CoroutineFeign with
truly non-blocking AsyncClient implementations (Http2Client,
AsyncApacheHttp5Client) instead of the thread-pool based
DefaultAsyncClient, with a regression test proving that decoding
runs off the caller thread.

Refs OpenFeign#2374
@ggoowlgns ggoowlgns changed the title feign-kotlin: fix suspend return types on blocking builder, document non-blocking AsyncClient usage, and support Flow returns feign-kotlin: add non-blocking WebClient AsyncClient and improve coroutine support Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

How to use AsyncCient for non blocking requests? feign-kotlin: Decoder type returns "Object" when method is suspending Support sse client

1 participant