Download Latest Version v4.0.0 source code.zip (748.4 kB)
Email in envelope

Get an email when there's a new version of Zuul

Home / v4.0.0
Name Modified Size InfoDownloads / Week
Parent folder
README.md 2026-07-08 3.4 kB
v4.0.0 source code.tar.gz 2026-07-08 375.2 kB
v4.0.0 source code.zip 2026-07-08 748.4 kB
Totals: 3 Items   1.1 MB 1

Zuul 4.0.0 replaces the RxJava Observable based async filter execution with CompletableFuture. RxJava 1.x is long past end-of-life, and moving to CompletableFuture lets the filter runner drop its custom RxJava Scheduler and Observer for plain JDK primitives.

Breaking changes

  • ZuulFilter.applyAsync now returns CompletableFuture<O> instead of Observable<O>. The Observable-based method is renamed to applyAsyncObservable and deprecated. Both have default bridge implementations on the interface, so a filter can override either one (#2075)
  • The debugRouting / debugRequest infrastructure is removed (#2078)
  • Connection draining is now event-based rather than channel-attribute based (#2161). Zuul-core fires a ConnectionCloseEvent down the pipeline instead of setting a channel attribute that every write has to check, so a graceful shutdown starts immediately on the event instead of waiting for the next response (an idle HTTP/1.1 connection can be closed right away), and OOS closes get random jitter to spread them out. The close handlers were reworked and CLOSE_AFTER_RESPONSE was removed - see Connection draining below
  • SessionContext no longer extends HashMap<String, Object>; it now wraps one via composition (#2171) and is @NullMarked (#2174). The common Map methods are re-exposed directly, but code that relied on it being a Map needs updating, and nullmarking turns the accessors @Nullable which can surface new NullAway warnings in consumers. The getRouteHost / setRouteHost / removeRouteHost accessors are removed - use the generic get/set with your own Key<T>.

Migrating a filter

A filter overriding applyAsync to return an Observable will not compile against 4.0.0. The minimal fix is a one-line rename:

Before:

:::java
@Override
public Observable<HttpResponseMessage> applyAsync(HttpRequestMessage request) {
    return service.call(request).map(this::toResponse);
}

After:

:::java
@Override
public Observable<HttpResponseMessage> applyAsyncObservable(HttpRequestMessage request) {
    return service.call(request).map(this::toResponse);
}

The default applyAsync bridges applyAsyncObservable back to a CompletableFuture, so the rest of the filter keeps working unchanged. The filter should be rewritten to return a CompletableFuture directly and delete applyAsyncObservable. applyAsyncObservable will be removed entirely in a later 4.x release.

Connection draining

Consumers that build their own channel pipelines need small updates:

  • The close and expiry handlers moved to the com.netflix.netty.common.close package
  • HTTP/2 connection close now lives on the connection pipeline, not the stream - drop any stream-level close-handler wiring
  • CLOSE_AFTER_RESPONSE / the allow_then_close rejection type is gone; throttled connections just close

Additional Changes

Also in this release: lazy hashCode caching (#2085), Objects.requireNonNull / @NonNull over preconditions (#2090), removal of unused classes (#2153), and a switch to typed SessionContext.Key<T> keys instead of stringly-typed context keys, both in the filter runner (#2167) and across SessionContext's own internal state (#2174).

Full Changelog: https://github.com/Netflix/zuul/compare/v3.6.21...v4.0.0

Source: README.md, updated 2026-07-08