Introduction
After strengthening your Java SE knowledge, stepping into the backend world with Servlets and then Spring/Spring Boot is a very sensible path.
Understanding the workings of the Servlet API and servlet containers like Jetty will help you grasp the foundations behind many behaviors of Spring Boot that sometimes seem “magical”.
In this article, as a backend developer, we will focus on the behind-the-scenes processes.
Key Concepts: Servlet and Servlet Container
Servlet
- A Java class designed to respond to requests over the HTTP protocol.
- Used to generate dynamic web content.
- Implements the
javax.servlet.Servlet
interface or extends thejavax.servlet.http.HttpServlet
abstract class.
Servlet Container (Web Container/Web Server)
- Software that manages the lifecycle of servlets.
- Receives HTTP requests and routes them to the appropriate servlet.
- Handles low-level tasks like network communication, thread management, and more.
- Examples include Jetty, Tomcat, Undertow. Spring Boot comes with Tomcat by default, but Jetty and Undertow can also be configured.
Jetty
Jetty is an open-source web server and servlet container developed by the Eclipse Foundation.
It is especially popular in embedded systems and microservice architectures:
- Lightweight and fast: Low memory usage and fast start-up.
- Embeddable: Can be included in a Java application (can be started with the
main()
method). - Flexible and extensible: Add/remove modules as needed.
- Asynchronous support: Supports Servlet 3.0+, WebSocket, NIO.
Request/Response Flow
Listening
- When Jetty starts, it begins listening on the configured port (e.g., 8080).
- The
ServerConnector
component opens a TCP socket.
Connection Acceptance
- The client requests a connection →
ServerConnector
accepts the connection.
Thread Allocation
- Jetty uses a Thread Pool (e.g.,
QueuedThreadPool
). - An available thread is assigned to the request and returned to the pool when the work is done.
Data Reading & HTTP Parsing
- Raw HTTP data is read from the TCP socket.
- Jetty’s HTTP Parser splits this data into parts like
method
,URL
,headers
, andbody
.
Request and Response Objects
- Jetty creates
HttpServletRequest
andHttpServletResponse
objects. request
→ contains all information about the requestresponse
→ used to construct the reply
Handler Chain & Routing
Incoming requests in Jetty pass through a Handler chain:
ServletContextHandler
orWebAppContext
→ maps the URL to the servlet.
Mapping can be defined in the following ways:
web.xml
– classic configuration with an XML file- Annotations –
@WebServlet("/path")
annotations
Servlet Lifecycle – init()
- When a servlet is called for the first time:
- It is loaded and the object is created.
- The
init(ServletConfig)
method is called only once. - Configuration and
ServletContext
are passed to the servlet.
Processing the Request – service()
- Jetty calls the
service(request, response)
method.
If the HttpServlet
class is used
service()
→ invokesdoGet()
,doPost()
,doPut()
, etc., according to the HTTP method.
Typical doGet()
or doPost()
logic
- You read data using
request.getParameter()
,getHeader()
,getInputStream()
. - Business logic is executed (DB query, API call, etc.).
- On the
response
object:setStatus()
setContentType()
setHeader()
- Write the response with
getWriter().println()
.
Response Commit & Sending
- When the servlet finishes the
service()
method:- Jetty sends the data in the
response
to the client in HTTP format. - Once commit happens, headers are sent and can no longer be modified.
- Jetty sends the data in the
Thread Release
- Returned to the thread pool and becomes ready for the next request.
Connection Close / Keep-Alive
- If
Connection: keep-alive
is present, the connection is kept alive. - Otherwise, the TCP connection is closed.
Jetty’s Thread Model: Parallel Processing
Thread Pool
- Jetty uses
QueuedThreadPool
for performance.
Purpose
- Instead of creating a new thread for each request, existing threads are reused.
How it Works
- When a request arrives, a thread is taken from the pool.
- After the work is done, it is returned.
- If all threads are busy, requests go into a queue.
Potential Problem: Pool Exhaustion
- During many long-running requests:
- Threads become full
- The queue grows
- Latency increases
➡️ Solution: The pool size can be configured.