Repository navigation
Conversation
Jersey was registered as a filter mapped to /* when the handler was initialized, and it ran the resource before passing the request on. Filters that an application added afterwards, through onStartup() or getServletContext().addFilter(), therefore ran only after the resource had executed and written its response, so a filter that ended the request without calling chain.doFilter() could not stop it. Jersey now runs as the servlet at the end of the filter chain, the way DispatcherServlet does in the Spring module. Every filter registered with the servlet context runs first, in registration order, whether it was added before or after initialize(). JerseyHandlerFilter no longer continues the chain once Jersey has written the response. Calling initialize() a second time no longer throws a NullPointerException.
This was referenced Oct 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue #, if available:
Description of changes:
Servlet filters registered on a Jersey handler now run before the JAX-RS resource, the way they run in front of a servlet in a servlet container.
The problem
JerseyLambdaContainerHandler.initialize()registered Jersey as a filter mapped to/*, andJerseyHandlerFilter.doFilter()ran the resource before callingfilterChain.doFilter(). The default factory methods (getAwsProxyHandler,getHttpApiV2ProxyHandler) callinitialize()before returning, so any filter an application added afterwards, throughonStartup()orgetServletContext().addFilter(), was placed after Jersey. It ran only once the resource had executed and written its response. A filter that ended the request without callingchain.doFilter()could change the status code, but the resource had already run and its body was returned.Building the handler with the constructor, registering filters, and then calling
initialize()avoided this, but nothing documented that the order mattered.The change
JerseyServlet, mapped to/*) passed todoFilter()as the terminal step of the chain. This is the same mechanism the Spring module uses forDispatcherServlet:FilterChainManageralready appends the servlet after every matching filter, so filters run first and in registration order, however and whenever they were registered.JerseyHandlerFilter.doFilter()no longer callsfilterChain.doFilter()after Jersey has written the response. The Jersey call moved to a package-privatehandle()that the servlet uses.initialize()because the filter chain reads its name from the servlet config when it builds the chain. The base class'sinitialize()isn't reused for this because it throws a checked exception that Jersey's publicinitialize()doesn't declare.initialize()a second time used to throw aNullPointerException, because the duplicate filter registration returned null. It now works.Reordering the calls inside
JerseyHandlerFilter.doFilter()was not enough: when a filter ends the request, control just returns to the Jersey filter, which can't tell and runs the resource anyway.Behavior changes for applications
chain.doFilter()now stops the request before Jersey runs, which is standard servlet behavior. In applications whose filters forgot to continue the chain, those requests no longer reach Jersey.getServletContext().getFilterRegistration("JerseyFilter")returns null. Jersey is registered as the servletJerseyServletinstead.No changes outside the Jersey module.
Testing
JerseyFilterChainTest, the same 96 cases as on main. Without the change, 48 fail and 3 error, the same as on main.gha_build.sh jersey true truepasses. The archetype's Maven build uses 1.9.6-SNAPSHOT and its tests pass.build.gradleand the pet-store sample use the range[1.9,2.0.0-M1), which on my machine resolved a leftover 1.10.0-SNAPSHOT from the local Maven cache. So I also built copies pinned to 1.9.6-SNAPSHOT: the archetype project builds with Gradle and its tests pass, and the sample builds with Maven and Gradle. CI has no such leftover, so the range resolves this branch there.Related
This is the 1.x backport. The production change is identical to the
mainPR apart from import lines. The test scaffolding differs in three ways, all forced by the older stack:javax.servletandjavax.ws.rsimports instead ofjakarta.initanddestroy, because the Servlet 3.1Filterinterface has no default methods.readValuethrows a checkedIOException.Review suggestion: review the
mainPR for the logic, and this one for the scaffolding delta.By submitting this pull request