ArchitectMind
  1. Home
  2. Tutorials
  3. Book 3 · Jakarta EE & Enterprise Java
  4. Volume I
  5. Web Server vs Application Server
Book 3 · Volume I — Web Tier & Web Application Technologies · Topic 1.2

Web Server vs Application Server

Web server, Servlet container, Application server, Responsibilities

8 min readStage 1 · Foundations6 interview questions
On this page
  1. 1. · Concept & Definition
  2. 2. · Why It Matters
  3. 3. · How It Works
  4. Deep Dive — The Path of One Request
  5. 4. · Java / Jakarta EE Code Examples
  6. 5. · Configuration, Defaults & Diagnostics
  7. 6. · Production Usage Patterns
  8. 7. · Common Pitfalls & Misconceptions
  9. 8. · Best Practices
  10. 9. · Architect's Perspective & Trade-offs
  11. 10. · Interview Q&A

1· Concept & Definition

The three runtime categories differ in how many enterprise services they provide. A web server serves static content and proxies HTTP. A servlet container (web container) runs Jakarta Servlet, Pages, EL and WebSocket components. An application server is a full Jakarta EE runtime: a web container plus CDI, REST, Persistence, Transactions, Messaging, Security and more, certified against a Jakarta EE profile.

Each runtime category adds services on top of the one below
Figure 1.2.1 — Each runtime category adds services on top of the one below

2· Why It Matters

  • Choosing the runtime determines which APIs are available without adding libraries, and who is responsible for integrating them.
  • It drives operational model: embedded containers in fat JARs vs. managed server installations with deployed WARs/EARs.
  • It shapes the network topology — which component terminates TLS, serves static assets and balances load.

The distinction also matters because people use the words loosely. Many design documents say "application server" when they mean an embedded Tomcat inside a Spring Boot JAR, and the difference only surfaces when someone assumes that a JTA transaction manager, a managed JMS connection factory or a JNDI data source is available. Being precise about the category tells reviewers which services the platform provides, which ones the team must select, configure and patch itself, and which component owns each concern at run time. It is also the first question in any runtime migration, because moving from a full server to a plain container means replacing every built-in service the application silently relied on.

3· How It Works

Web server

A web server (nginx, Apache httpd, Caddy) is optimised for high-concurrency network I/O. It does not run Java components; in Java architectures it acts as a reverse proxy in front of containers.

CapabilityTypical implementation
Static contentHTML, JS, CSS, images with sendfile and caching headers
Reverse proxy & load balancingproxy_pass / mod_proxy to container pools, health checks
TLS terminationCertificates, OCSP stapling, HTTP/2 and HTTP/3 to clients
Compression & cachinggzip/Brotli, proxy caching of cacheable responses
Edge securityRate limiting, request size limits, IP filtering, basic WAF rules

Servlet container

A servlet container implements the Jakarta Servlet specification and related web specifications. It manages connectors (network listeners), thread pools, request mapping, the filter chain, sessions and component lifecycle.

ContainerCurrent line (Jakarta EE 11 era)Notes
Apache TomcatTomcat 11 — Servlet 6.1, Pages 4.0, WebSocket 2.2Most widely used; embedded in Spring Boot by default
Eclipse JettyJetty 12 — multiple EE levels on one coreLightweight, strong async/HTTP/2 support
UndertowUsed by WildFlyNon-blocking core, embeddable

Application server

An application server provides the full Jakarta EE programming model and manages shared resources such as connection pools, transaction managers, JMS destinations and security realms. Modern servers are modular and can also run as runnable JARs.

ServerProfile supportCharacteristic
WildFly / JBoss EAPPlatform, WebModular subsystems; Galleon provisioning trims the runtime
Open Liberty / WebSphere LibertyPlatform, Web, CoreFeature-based: enable only required features
Payara Server / MicroPlatform, WebGlassFish-derived; Micro edition for containers
Eclipse GlassFishPlatform, WebCompatible implementation used for specification work

Responsibilities

A typical production topology with responsibilities split between edge, containers and external state
Figure 1.2.2 — A typical production topology with responsibilities split between edge, containers and external state
ResponsibilityWeb serverServlet containerApplication server
Static filesPrimaryPossible (default servlet)Possible
TLS terminationPrimarySupportedSupported
Servlet / Pages / WebSocket—YesYes
CDI, REST, Validation—Add librariesBuilt in
JTA transactions, JMS, EJB, Batch—Add libraries (limited)Built in
Managed resources (pools via JNDI)—BasicFull, admin-managed

Deep Dive — The Path of One Request

Following a single HTTPS request through a typical topology shows where each runtime category does its work. The client opens a TLS connection to nginx or a cloud load balancer, which decrypts it, answers directly if the URL matches a static location, and otherwise picks a healthy upstream and forwards the request over a pooled keep-alive connection, adding X-Forwarded-For and X-Forwarded-Proto headers so the container knows the original client and scheme.

In Tomcat, the connector's acceptor thread accepts the socket and registers it with a poller, which uses non-blocking I/O to wait until bytes are readable. Only then is the socket handed to a worker thread from the executor. The worker parses the request line and headers, maps the URL to a context and a servlet, runs the filter chain and calls service(). It stays busy until the response is committed, unless the servlet uses asynchronous processing to release it while waiting for slow back-end work.

An application server adds layers inside that call. Before a business method runs, container interceptors start or join a JTA transaction, propagate the authenticated identity and activate CDI contexts, and managed resources such as data sources and JMS connection factories come from server-wide pools. A servlet container with libraries can provide similar behaviour, but each piece is wired and configured by the application rather than by the platform.

4· Java / Jakarta EE Code Examples

nginx.conf (excerpt) — web server as TLS terminator, static server and reverse proxy
# nginx reverse proxy in front of two Tomcat instances
upstream app_pool {
    server 10.0.1.11:8080 max_fails=3 fail_timeout=10s;
    server 10.0.1.12:8080 max_fails=3 fail_timeout=10s;
}
server {
    listen 443 ssl http2;
    location /static/ { root /var/www; expires 7d; }           # served by nginx
    location / {
        # forwarded to containers
        proxy_pass http://app_pool;
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
server.xml — making the container proxy-aware
<!-- Tomcat server.xml: trust forwarded headers only from the proxy network -->
<Valve className="org.apache.catalina.valves.RemoteIpValve"
       internalProxies="10\.0\.0\.\d{1,3}"
       remoteIpHeader="X-Forwarded-For"
       protocolHeader="X-Forwarded-Proto" />
Feature-based application server configuration
<!-- Open Liberty server.xml: enable only the features the application uses -->
<featureManager>
    <feature>servlet-6.1</feature>
    <feature>restfulWS-4.0</feature>
    <feature>cdi-4.1</feature>
    <feature>persistence-3.2</feature>
</featureManager>

5· Configuration, Defaults & Diagnostics

SettingTomcat defaultMeaning
maxThreads200Maximum request-processing threads per connector
minSpareThreads10Threads kept ready
maxConnections8192 (NIO)Connections accepted and kept open
acceptCount100OS backlog when all connections are busy
connectionTimeout20000 ms (shipped server.xml)Wait time for request line after accept

Most runtime-layer incidents first appear as 502 or 504 responses at the proxy, so diagnose from the outside in. A 502 means the proxy did not get a valid response, often because the container closed an idle keep-alive connection that the proxy reused, or because the instance was restarting. A 504 means the upstream was slower than the proxy's read timeout. Inside the container, compare busy threads with maxThreads, log request duration in the access log, and take a thread dump when the pool is saturated to see what the workers are waiting for.

Diagnosing the request path
# Busy request threads (Spring Boot needs
# server.tomcat.mbeanregistry.enabled=true for this metric)
curl -s localhost:8080/actuator/metrics/tomcat.threads.busy

# Tomcat access log pattern with request time in ms (%D)
#   %h %t "%r" %s %b %D

# nginx: log upstream time separately from total request time
log_format timed '$remote_addr "$request" $status '
                 'rt=$request_time urt=$upstream_response_time';

# Thread dump of a saturated container
jcmd <pid> Thread.print > threads.txt

6· Production Usage Patterns

  • Edge proxy + embedded container per service: each microservice packages Tomcat/Jetty/Undertow; nginx, an ingress controller or cloud LB handles TLS and routing.
  • Shared application server with multiple WARs: common in established enterprises; centralised resources and administration.
  • Trimmed application server in containers: Liberty features or WildFly Galleon layers produce small images with full Jakarta EE APIs.
  • CDN for static assets with the Java tier serving only dynamic content and APIs.

7· Common Pitfalls & Misconceptions

  • Calling Tomcat an "application server" in design documents — it is a servlet container; missing services must be added and integrated.
  • Serving large static files through the Java tier — wastes request threads and heap.
  • Double compression or double TLS configured accidentally on both proxy and container.
  • Deploying many unrelated applications on one server — shared failure domain and coupled upgrades.

8· Best Practices

  • Choose the smallest runtime that provides the required specifications (Core/Web profile or container + selected libraries).
  • Put a reverse proxy or managed load balancer in front of Java runtimes for TLS, static content and protection.
  • Keep one application per runtime in container-based deployments for isolation and independent scaling.
  • Align container thread pools, proxy timeouts and load-balancer timeouts end to end.

9· Architect's Perspective & Trade-offs

CriterionServlet container + librariesJakarta EE application server
Footprint & startupSmall, fastLarger (reduced by feature trimming)
API integrationTeam selects and integratesCertified, integrated, tested together
OperationsApp owns config in codeAdmin console/CLI, managed resources
PortabilityTied to chosen librariesPortable across certified servers
Typical fitMicroservices, Spring BootEnterprise suites, JTA/JMS-heavy integration

10· Interview Q&A

Q1BasicConcept

What is the difference between a web server and a servlet container?

A web server handles HTTP at the network edge but does not run Java code, while a servlet container hosts Java web components. nginx, Apache httpd and Caddy accept connections, terminate TLS, serve static files, compress responses and proxy requests to upstream servers; they are optimised for very high concurrency with little memory per connection. A servlet container such as Tomcat or Jetty implements the Jakarta Servlet specification: it maps URLs to servlets, runs filters, manages sessions and component lifecycles, and gives each request a worker thread. Most production deployments use both, with nginx or a managed load balancer in front and one or more containers behind it.

Q2IntermediateTricky

Is Tomcat an application server?

Not in the Jakarta EE sense. Tomcat 11 is a servlet container: it implements Servlet 6.1, Pages 4.0, Expression Language and WebSocket, which is only part of the Web Profile. It does not provide CDI, Jakarta REST, Persistence, JTA transactions or messaging out of the box, so applications add them as libraries, such as Weld, Jersey and Hibernate, or use Spring, which supplies its own equivalents. Calling Tomcat an application server is harmless in conversation, but in a design document the distinction matters, because every missing service becomes something the team must choose, integrate, configure and patch. Apache TomEE is the variant that adds Jakarta EE profile support.

Q3IntermediateConcept

Why place nginx in front of a Java container?

Because it handles edge concerns more efficiently than Java request threads and keeps them in one place. nginx terminates TLS and HTTP/2 or HTTP/3, serves static assets with sendfile and caching headers, compresses responses, and buffers slow clients, so a container thread is not held while a mobile user uploads slowly. It load-balances across instances with health checks and enforces rate limits, request size limits and basic filtering before traffic reaches the application. That leaves container threads for dynamic work. In Kubernetes or the cloud the same role is usually played by an ingress controller or a managed load balancer, so a separate nginx tier is not always needed.

Q4Senior EngineerTricky

Which settings must be aligned between the proxy and the container?

Timeouts, connection handling, size limits and forwarded headers. The proxy read timeout must exceed the slowest legitimate request, or users see 504s while the container keeps working. The container's keep-alive timeout should be longer than the proxy's idle timeout for upstream connections; otherwise the container closes a socket the proxy is about to reuse and some requests fail with 502. Maximum request body sizes must match, or uploads are rejected at one layer with confusing errors. Proxy connection limits and container thread pools should be sized together. Finally, the container must trust X-Forwarded-Proto and X-Forwarded-For only from the proxy, or redirects point to http.

Q5Technical ArchitectTrade-off

When would you choose a full application server over an embedded container?

I choose a full application server when the application genuinely uses several integrated Jakarta EE services together: distributed JTA transactions across a database and a message broker, JMS with message-driven beans, EJB timers, Jakarta Batch, or server-managed connection pools and security realms that operations administer centrally. Integrating those by hand on a bare container is possible but error-prone, while a certified server provides them tested together. For a typical REST microservice with one database, an embedded container with a few libraries, or a framework such as Spring Boot or Quarkus, is simpler and starts faster. Open Liberty and WildFly can be trimmed to only the features needed, which narrows the gap.

Q6Solution ArchitectDesign

How do you decide the runtime strategy for a portfolio of Java applications?

I would classify applications by what they need from the runtime and how they are operated, then standardise a small set of supported options. Typically that means an embedded-container image, such as Spring Boot or a MicroProfile runtime, for stateless services, and a trimmed Jakarta EE server such as Open Liberty or WildFly for integration-heavy systems that use JTA, JMS or batch. Each option gets a platform template with a base image, hardened configuration, observability, a patching cadence and an owner. Legacy shared servers get a migration path rather than new tenants. Every extra runtime adds patching, skills and support contracts, so variety should be a deliberate choice.

Newsletter

Get new tutorials every week

New ArchitectMind tutorials and interview questions, once a week. No spam, unsubscribe anytime.

Almost done! Check your inbox and click the confirmation link to complete your subscription.