-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Upgrade Guide 4.0
Zuul 4 keeps the same Netty platform as Zuul 3, but 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.
Key changes:
-
ZuulFilter.applyAsyncnow returnsCompletableFuture<O>instead ofObservable<O> - The
debugRouting/debugRequestinfrastructure has been removed - Connection draining is now event-based rather than channel-attribute based
-
SessionContextno longer extendsHashMap<String, Object>- it wraps one via composition and is@NullMarked - JDK v21+ support only (unchanged from Zuul 3)
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.
A filter overriding applyAsync to return an Observable will not compile against 4.0.0. The minimal fix is a one-line rename:
Before:
@Override
public Observable<HttpResponseMessage> applyAsync(HttpRequestMessage request) {
return service.call(request).map(this::toResponse);
}After:
@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.
This is only a stopgap. The filter should be rewritten to override applyAsync returning a CompletableFuture directly and delete the applyAsyncObservable method, avoiding the bridge:
@Override
public CompletableFuture<HttpResponseMessage> applyAsync(HttpRequestMessage request) {
return service.callAsync(request).thenApply(this::toResponse);
}applyAsyncObservable will be removed entirely in a later 4.x release.
The debugRouting / debugRequest infrastructure has been removed. This feature stored per-filter execution traces and request/response dumps in SessionContext, gated by debugRouting and debugRequest boolean flags. It added overhead on every filter execution (message cloning, context diffing) and was not used in production.
Gone as part of this: the Debug class, SurgicalDebugFilter, the sample DebugFilter / DebugRequest / DebugResponse / Stats filters, the debugRouting / debugRequest / debugRequestHeadersOnly fields and methods on SessionContext, and the related constants, headers and config properties. If you relied on any of these, remove the references.
Connection draining is now event-based rather than channel-attribute based. Zuul-core fires a ConnectionCloseEvent down the pipeline instead of setting a channel attribute that every write has to check. 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.
Consumers that build their own channel pipelines need small updates:
- The close and expiry handlers moved to the
com.netflix.netty.common.closepackage - HTTP/2 connection close now lives on the connection pipeline, not the stream - drop any stream-level close-handler wiring
-
CLOSE_AFTER_RESPONSE/ theallow_then_closerejection type is gone - throttled connections just close
SessionContext no longer extends HashMap<String, Object> - it now wraps one via composition and is @NullMarked. The common Map methods (get, put, remove etc) are re-exposed directly, so most call sites are unchanged, but code that relied on SessionContext actually being a Map (passing it where a Map is expected, calling Map methods that weren't re-exposed) needs updating.
Nullmarking turns the accessors @Nullable, which can surface new NullAway warnings in consumers - handle the nulls at the call site.
The getRouteHost / setRouteHost / removeRouteHost accessors have been removed. Use the generic get / set with your own typed key:
private static final SessionContext.Key<URL> ROUTE_HOST = SessionContext.newKey("routeHost");
context.set(ROUTE_HOST, routeHost);
URL routeHost = context.get(ROUTE_HOST);getRouteVIP and the other VIP accessors are unchanged.
Also in this release: lazy hashCode caching relying on the JVM instead of eagerly caching string hashes, a switch to Objects.requireNonNull / @NonNull over Guava and Spectator Preconditions, removal of unused classes, and a switch to typed SessionContext.Key<T> keys instead of stringly-typed context keys, both in the filter runner and across SessionContext's own internal state.
A Netflix Original Production
Tech Blog | Twitter @NetflixOSS | Jobs