The Servlet API has clearly been hugely successful and influential. It has been a key part of Java’s success on the server, and is the underlying base on which a broad range of higher-level web-frameworks have been built. It has also stood the test of time, surviving largely unchanged from the early days of programmatically printing lines of HTML through to the world of tag libraries, portlets, JSF, AJAX, mash-ups, web-services and the like. That’s something that can’t be said about all APIs from that far back.
But I’m starting to wonder if the time has come to take a fresh look at this API and modernize it – or even replace it with something radically different.
So what’s wrong?
(Some of this bit’ is rather dry… feel free to skip to the wild-and-wacky stuff after it if that’s more your thing)
As with any API, over time there has been a gradual accretion of features that weren’t originally catered for, or weren’t adequately understood, and there has been the usual general wear-and-tear on the API due to deprecations, adjustments, clarifications, assorted tweaks etc. But in addition, the underlying Java platform and its libraries have improved significantly; the nature of the API’s clients and related technologies have changed quite dramatically (as has the API’s general place in the overall scheme of things); and ideas on API design have progressed.
So here are some particular examples of things that I think look outdated or otherwise in need of improvements or redesign (in somewhat random order, and by no means intended to be comprehensive):
- The current API was designed before Collections and generics. There’s at least one use of Map, but in general the support for attributes, request parameters, and init parameters and the like all use repetitive patterns of get/set/getName/getValues methods, and they return arrays and untyped Enumerations. This could surely be simplified and improved by taking full advantage of Collections and generics. There may also be other places where generics could tighten things up (e.g. to enforce matching request/response subclasses).
- The current API design also precedes JDK 1.4’s introduction of the java.net.URI class. It uses strings to represent URLs, URIs and their component parts, with lots of inconsistent terminology and incomplete rules and explanations (for example, where path parameters and/or fragments are permitted and how they are handled, which strings are and aren’t URL-encoded, how absolute/context-relative/request-relative paths are identified and “resolved” etc). A lot of this could probably be made more general, more explicit, and probably rather simpler by using java.net.URI wherever appropriate.
- Java SE 6 also introduces a java.net.Cookie class and supporting classes for managing sets of cookies, which look rather more capable than javax.servlet.Cookie and presumably render it redundant. There are probably lots of other Java SE classes that weren’t available when the Servlet API was designed but that it could now take advantage of (subject to which Java SE versions have to be supported).
- The use of inheritance to separate HTTP-specific features from more general protocol-independent base classes looked like a good idea originally but the more I see of it the worse it seems. In practice, everthing seems to be designed around HTTP anyway, and the notional ability to support other protocols is rather vague and under-developed whilst adding considerable complexity. Despite the logical division, the protocol-independent base classes often specify particular behaviour that is required when the protocol is HTTP, or worse just silently assume HTTP and refer to HTTP-only features (for example, request-dispatching deals with requests of any kind, but specifies the “adjustment” of features that are only present in HTTP requests). Conversely, other features are restricted to the HTTP classes even when they might apply more broadly (such as the request having a URL). Similarly, many deployment descriptor settings are only relevant for HTTP and have uncertain meaning for non-HTTP requests (what do URL-patterns mean for non-HTTP requests if such requests aren’t considered to have a URL? how do you “map” such requests?). More generally, there doesn’t seem to be any clear picture of just how broad a range of protocols the API is and isn’t intended to accommodate. As a result, the choice of what features are common to all protocols seems somewhat arbitrary, whilst still leaving the suitability of the base classes for any actual protocols other than HTTP rather uncertain. There are lots of other problems with this inheritance-based approach (for example, the use of “wrapper” classes also becomes rather awkward once you need separate wrapper classes for HTTP and non-HTTP classes, and this can end up leading to some horrible class hierarchies). In practice, the API seems to be almost synonymous with HTTP processing. Maybe it would be simpler overall if it really were just HTTP-specific, with other protocols being considered as outside its scope. Or maybe the protocol-specific support should be provided by composition rather than inheritance (e.g. using a set of protocol-specific “extensions” as pluggable sub-components to provide the relevant combination of additional features).
- The general quality of the Javadoc seems surprisingly poor. Maybe it’s just me, but there seems to be a lot of it that looks ok at first glance but falls apart under detailed examination. Lots of minor mistakes, typos and inconsistencies, of course (including methods that appear to mistakenly have some or all of another method’s Javadoc). But also too many methods that don’t specify what range of argument values are allowed (for example, whether null or empty-string arguments are allowed, and if so how such values are treated). Too many under-specified interactions between different parts of the API (like whether an HTTP response’s “getCharacterEncoding” method is or isn’t affected by explicit use of the “setHeader” method to set its content-type header). Too many vague terms and references to things that aren’t accessible through the API (like references to “the current request URI” in classes that don’t otherwise have any explicit relationship to a request and don’t explain what “current” actually means in the face of request dispatching and wrappers).
- Unit-testing of code that uses the Servlet API is far too difficult. The application code depends on objects that are “magically” constructed by the container, typically with lots of read-only features and complex internal behaviour and interactions, and potentially dependent on container configuration. You can test an application’s overall behaviour by deploying it and throwing HTTP requests at it, or you can use a tool such as Cactus to run more detailed tests inside the container, or you can use mocks/stubs of some kind to simulate what you think the objects will be, how they will behave, and what the container will do. But all of these approaches have limitations and shortcomings, and make testing Servlet code far harder than normal testing of POJOs. I don’t know the entire answer to this, but there must be way of making the API more “POJO-friendly” and more amenable to normal testing tools and techniques.
- There are a few areas that seem to have turned out to be rather more complex than originally thought, and which seem to have continual adjustments and clarifications. Some of these have become a bit more complex than is desirable, whilst other still have rough edges and uncertainties. Particular things that come to mind are the handling of content-type and character-encoding (interaction of setters/getters, HTTP headers, request/response state, explict resetting of request, implicit resetting of request due to forwarding etc); the interaction between cross-context request dispatching and session handling (and its implications for session-tracking mechanisms); and how to properly “wrap” a response bearing in mind all the complex interactions between its methods and the possibility of other wrappers around it (even the standard HttpServlet implementation gets this wrong). I’d imagine that having been through all this once, a redesign of the API could come up with a simpler and more complete solution for many of these issues.
- The request and response are represented by separate objects but are always processed together. Normally the same type-checks, casts, wrapping etc must be applied to both. They also each have methods that depend on particular aspects of the other’s state, despite neither having any explicit access to each other (for example, there are situations where the response’s sendRedirect method depends on the request’s URL, and there are methods on the request that depend on whether the response has been committed or not). It might be simpler to combine them into a single object, or at least have them as sub-components of a single object (as per the “Exchange” object in Sun’s com.sun.net.httpserver package).
- Although much of the API implementation must be left to the container, there are quite a few API methods that are related or can be based on each other. Often these are minor variations on each other or have slightly different arguments but provide the same functionality, or are roughly equivalent methods provided in different places. There are also probably parts of the API that could reasonably be provided independently of the container implementation (such as “attribute” handling). So maybe there’s an opportunity to separate out a “Service Provider Interface” that defines a minimal set of facilities that the container must provide, and the Servlet API itself can provide a standardized implementation of how the full API is built up from these. This might also make it easier to provide a mock/stub “container” for use during testing.
- There are also lots of new and long-outstanding requirements that might heavily influence the API if it were being designed from scratch. For example, maybe more direct and explicit support for web-services and SOAP, AJAX and XMLHttpRequest would make sense, or better support for component-based frameworks such as JSF (I’m not immediately aware of any particular areas where the current API design is a problem for this, or where a different API design could help, but I suspect there might be some). More generally, we’re probably due a change of emphasis from sending complete HTML pages to the client towards providing services and data for other applications and “rich” clients.
You may well disagree with some of the above, but you can probably also think of many more.
I think it’s quite telling that the com.sun.net.httpserver package introduced in Sun’s JDK 6 is very different in design from the Servlet API. OK, it’s intended for rather different purposes, and it is effectively the “container” itself rather than the API for talking to the “container” (and being a com.sun package, presumably isn’t actually an official part of Java SE). But its features do substantially overlap with the Servlet API, and its design does seem to recognise and address some of the issues listed above. For example, it uses an HttpExchange class to represent a request and response together; it uses a distinct Map-based “Header” class to represent a set of HTTP headers; and it makes no pretense of being for anything other than HTTP. If all was well with the Servlet API, maybe this wouldn’t be needed as an entirely separate and unrelated package, or maybe it wouldn’t use such a completely different design. Maybe it would just be a lightweight servlet-container and implement the Servlet API, or at least the relevant subset of it.
On a slight side-track, isn’t it a bit worrying that we now have the Servlet API classes in Java EE, overlapping HTTP-related classes in Java SE that the Servlet API doesn’t actually use, and a separate com.sun HttpServer in the JDK that takes a quite different and unrelated approach?
The bigger picture, and speculations.
In more general terms, I’m no longer sure exactly what type of “service” the Servlet API is intended to address, what particular protocols (if any) it should be tied to, or even whether the Java SE vs. EE split really makes sense anymore.
Back in the late 90’s it clearly made sense to provide an API through which code could run inside a web-server and respond to HTTP requests by writing out lines of HTML. Server-side web programming was largely CGI-based, beyond which Microsoft had ISAPI and Netscape had NSAPI. Java needed a standard API for doing something along the same lines. The clients were all “dumb” browsers that needed to be spoon-fed the actual text to be displayed, with no real “presentation layer” processing of their own.
But I don’t think much of that holds any more, or at least not to the point where we need a major API focused purely on that particular model. What we now have are increasingly smart clients, even where they are web browsers – and increasingly the client is just as likely to be another server application, or a “thick” client on a desktop, phone, or some other device.
So I suspect Google’s GWT, the various “rich client” approaches (including, still, Java applets), and other such browser-based presentation layers are the way forward. That is, provide a presentation layer that the client can run itself, and the back-end services are just that: back-end services. The communication might still be over HTTP for convenience, but the meaningful bit is its content – be it web-service XML stuff, XMLHttpRequest, JSON, or whatever.
From that point of view, the nature and purpose of a “Servlet API” starts to look rather different, and it’s easy to see how the current approach is ignoring where the real action is and leaving it to a confusing mess of non-standardized, “higher-level” frameworks. It’s also hard to see any fundamental differentiation between the Servlet API and EJB Session Beans, or why such services should be considered “web tier”.
To mix things up even further, we then also have an HTTP server appearing in the JDK, and desktop applications that might increasingly need simple ways to utilize multi-core CPUs without getting bogged down in concurrency issues or worrying about synchronization (sound familiar?).
So I’m wondering if what we really need is a more general, protocol-independent concept of a “service” that supports multiple concurrent requests. Typically but not necessarily over a network, possibly but not necessarily with support for sessions/conversations, and that can be called either synchronously or asynchronously. Maybe suitable for internal services within desktop apps just as much as for server-based access over the internet. Ideally all POJO-based. It’s almost a language-level thing. Then have protocol-specific adaptors for talking to/from such services. OK, I’m dreaming and completely out of my depth here, but it feels like it might unify a lot of things if the differences in their characteristics doesn’t render it unworkable.
So where do we go from here?
Back to reality: I’m not expecting anyone to jump up and down and immediately start replacing the Servlet API with a whole new API. Let alone replace the Servlet API, Session Beans, Message Beans, the com.sun.net.httpserver package, and all sorts of other stuff with some kind of wizzy new native language support for POJO-based “services”… maybe aim for Java 9 or 10 instead :-).
But I do think that even if we stick with a recognizable Servlet API, there’s a lot that could be done to substantially modernize it and provide a better design from which it can move forward. We’re surely long overdue the kind of revamp and simplification that we’ve seen in EJB. I’m convinced there’s a really small and tidy API buried in there somewhere!
Alternatively, maybe it would be more practical to implement a redesigned API as a layer on top of the existing API, whilst remaining highly interoperable with it (any takers?).
Or maybe it’s all beside the point… maybe the Servlet API now just sits there gathering dust as the long-ignored but somewhat cranky foundation on which everything else is built, until eventually something else grows to the point where the Servlet API can ride off into the sunset.