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
…ilter Review feedback on aws#1723. getServlet() returned null because Jersey ran as a filter and there was no servlet to hand back. It now returns the servlet the handler routes to, which is the one registered on the servlet context. JerseyHandlerFilter is no longer registered as a filter, so its class documentation no longer describes it as one, and doFilter() is deprecated: it runs Jersey and ignores the chain, because Jersey writes the response. The Filter interface stays implemented, since the class is public.
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 Jersey archetype project builds with Maven and Gradle against 2.1.7-SNAPSHOT and its tests pass, and the Jersey pet-store sample builds with Maven and Gradle.pom.xmluses an uncapped range,[2.0-SNAPSHOT,), so its Maven build resolves the newest 3.x version rather than this branch (Gradle is capped below 3.0 and resolves 2.1.7-SNAPSHOT). A copy of the sample pinned to 2.1.7-SNAPSHOT also builds with Maven.Related
This is the 2.x backport. The production change is identical to the
mainPR. The only scaffolding difference is in the new test: Jackson 2'sreadValuethrows a checkedIOException, so the response is parsed inside the existing try block.Review suggestion: review the
mainPR for the logic, and this one for the scaffolding delta.By submitting this pull request