Thursday, May 27, 2021 - 08:38
  • Share this article:

Now that the Jakarta Servlet 5.0 specification has been released as part of Jakarta EE 9, and all of the meta work is complete, it’s time to start thinking about how the specification should evolve from a functionality perspective. It’s the ideal time to add new features, and to remove others.

I’ll provide more rationale in the article, but here’s a summary of the features I would like to see deprecated or removed before the Jakarta Servlet specification is extended:

  • Cross-context dispatch is deprecated and existing methods return null. Once a request is matched to a context, it is only ever associated with that context, and the getServletContext() method returns the same value no matter what state the request is in.
  • The Wrapper Object Identity requirement is removed. The request object is required to be immutable to the methods affected by a dispatch and can be referenced by asynchronous threads.
  • The RequestDispatcher.include(...) is deprecated and replaced with utility response wrappers. The existing API can be deprecated and its implementation changed to use a request wrapper to simulate the existing attributes.
  • The special attributes for FORWARD, INCLUDE, and ASYNC are removed from normal dispatches. Utility wrappers are provided to simulate these attributes if needed for backwards compatibility.
  • The getDispatcherType() method is deprecated and returns REQUEST, unless a utility wrapper is used to replicate the old behavior.
  • Servlet API methods that mutate state are only callable from request-serialized container-managed threads and otherwise throw IllegalStateException. New AsyncContext.dispatch(Runnable) and AsyncContext.getExecutor() methods provide access to request-serialization for arbitrary threads, lambdas, and runnables.

Here’s a closer look at some of the key opportunities I see to improve Jakarta Servlet. My descriptions here are highly condensed versions of much more in-depth insight I provided in an article I wrote for Webtide.

Backwards Compatibility for the Future

The original Jakarta Servlet specification was created in 1997, and it’s amazing that more than two decades later, a servlet written against version 1.0 will run in the latest Jakarta EE containers.

However, if the Jakarta Servlet specification is to continue to be relevant, it needs to compete with state-of-the-art HTTP servers that don’t support decades of Java EE legacy. Legacy is a strength and a weakness, and now is the time to focus on the former.

The namespace change from java.* to jakarta.* has already introduced a discontinuity in backwards compatibility. Keeping Jakarta Servlet 5.0 identical in all but name to version 4.0 was the right approach to support automatic application porting. But it has also given developers a reason to consider alternatives, so now is the right time to ensure Jakarta Servlet 6.0 is a good basis for the future of Jakarta EE Servlets.

Getting Cross About Cross-Context Dispatch

Every issue discussed in this article becomes more complex when cross-context dispatch is considered. It introduces additional class loaders, different session values in the same session ID space, different authentication realms, and authorization bypass. And don’t even get me started on the needless, mind-bending complexities of a context that forwards to another then forwards back to the original…

Modern web applications are now often broken up into many microservices, so the concept of one web app invoking another is not bad, but the idea of those services being co-located in the same container instance is neither a general, nor flexible, assumption.

By all means, Jakarta Servlet should support a mechanism to forward or include other resources, but ideally, this should be done in a way that works equally well for co-resident, co-located, and remote resources.

I’ll just assume cross-context dispatch is already dead, and ignore the issue in the discussions that follow. See my recommendation for deprecating cross-context dispatch above.

Exclude Include

Ultimately, there is no need for an include API given that the specification already has a reasonable forward mechanism that supports wrapping. The ability to include one resource in the response of another can be provided with a basic wrapper around the response, as shown below.

@WebServlet(urlPatterns = {"/servletA/*"})

public static class ServletA extends HttpServlet

{

@Override

protected void doGet(HttpServletRequest request,

HttpServletResponse response) throws IOException

{

request.getRequestDispatcher("/servletB/infoB")

.forward(request, new IncludeResponseWrapper(response));

}

}

This type of response wrapper could also ensure the included content type is correct and deal with error conditions rather than ignoring an attempt to send a 500 status. To assist with porting, the include can be deprecated, and its implementation replaced with a request wrapper that reinstates the deprecated request attributes, as shown below.

@Deprecated

default void include(ServletRequest request, ServletResponse response)

throws ServletException, IOException

{

forward(new Servlet5IncludeAttributesRequestWrapper(request),

new IncludeResponseWrapper(response));

}

Dispatch the DispatcherType

The concept of DispatcherType should be deprecated, and it should always return REQUEST. Backwards compatibility can be supported by applying a wrapper that determines the deprecated DispatcherType only if the method is called.

Unravelling Wrappers

The feature described in specification section 6.2.2, Wrapping Requests and Responses, really needs to be revised.

The core concept of wrappers is sound, but the “wrapper object identity” requirement has little utility, yet significant impacts, on implementation correctness and performance. It significantly impairs container implementation for a feature that can be rendered unusable by a wrapper applied by another filter. It should be removed from Jakarta Servlet, and requests passed in by the container should be immutable.

New APIs can be passed on objects set as request attribute values that pass through multiple other wrappers, coexist with other new APIs in attributes, and do not require the core request methods to have mutable returns.

Asynchronous Life Cycle

The current specification of the asynchronous life cycle is the worst of both worlds for container implementation.

On one hand, developers must implement the complexity of request-serialized events, so there can only be a single container-managed thread for a given request. On the other hand, an arbitrary application thread can concurrently call the API, requiring additional thread-safety complexity. All the benefits of request-serialized threads are lost by the ability of arbitrary other threads to call the Servlet APIs.

The fix is twofold:

  • Make more Servlet APIs immutable so they are safe to call from other threads.
  • Ensure any API that does mutate state can only be called from request-serialized threads.

Learn More and Get Involved in Jakarta EE

For more insight into why I’m making these recommendations, read the full article. For more information about the contents of Jakarta Servlet, review the specification.

The Jakarta EE community welcomes everyone’s input on the next versions of the Jakarta EE specifications. To join the conversation, subscribe to our mailing lists, check out our community calendar, and read our blogs.

About the Author

Greg Wilkins

Greg Wilkins

Greg Wilkins is a project lead for the Eclipse Jetty Servlet Engine and HTTP Server, as well as a key contributor to the CometD server push framework and several other open source projects. Greg is an active participant in the standards processes at the Java Community Process and the IETF. Greg was a founder of Mort Bay Consulting and Webtide.com, and is now a CTO at Webtide.