Why Overriding HttpServlet’s service() Is a Bad Idea: Caching Insights
This article explains why developers should avoid overriding the HttpServlet service() method, illustrating how the default implementation handles HTTP cache negotiation, showing concrete servlet examples, and demonstrating the significant traffic and performance benefits of letting the container manage caching automatically.
Usually the story starts like this: a seasoned programmer tells his grandson, “If you ever write a servlet, you’d better not override the service method.”
Why shouldn’t we override the service method?
If you have ever wondered about this question without finding an answer, this article may give you some insight.
Let’s look at a concrete example
Opening Chrome’s developer tools and refreshing a static page shows one request returning status 200 and the rest returning 304. The 200 response includes the resource, while 304 indicates the browser used its cached copy.
The core idea comes from HTTP cache negotiation:
When the browser first requests a resource (no If-Modified-Since header), the server responds with Last-Modified indicating the resource’s last change time.
On subsequent requests (e.g., pressing F5), the browser sends If-Modified-Since to ask whether the resource has changed since that time.
If the resource has not changed, the server returns status 304 and the browser reuses its local cache.
This mechanism means static resources that haven’t changed are not transferred repeatedly, saving bandwidth.
Understanding cache negotiation makes it easy to see why overriding service() is undesirable. Let’s examine a more complex servlet example.
We configure a simple servlet MeServlet in web.xml:
And the servlet implementation:
Each refresh returns status 200, and the full HTML is sent every time, because the dynamic response lacks Last-Modified information, so the browser cannot perform cache negotiation.
We modify the servlet to override getLastModified():
Even after this change the response is still 200 because the container does not automatically use the overridden method.
Finally we override doGet() and perform the same logic there:
Now the response includes Last-Modified, and a subsequent request receives status 304, confirming that the default service() implementation already handles cache negotiation for you.
The practical benefits are substantial. In a test page with 45 requests totaling 198.93 KB, a simple refresh reduced the traffic to 36 requests and 23.62 KB. Multiplying the saved bandwidth by thousands of users can save gigabytes of data transfer.
(198.93 KB – 23.62 KB) × 9960 ≈ 1.66 GB of traffic saved for a single page view.
Beyond bandwidth, reduced traffic improves server load and user experience. Moreover, by not overriding service() you avoid unintentionally disabling handling of other HTTP methods (POST, HEAD, etc.), which can improve security.
In summary, keep the default service() implementation to retain built‑in cache negotiation and method handling, unless you have a specific reason to replace the entire request processing logic.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
