Series MapLesson 13 / 50
Focus mode active/Press Alt+Shift+R to toggle/Esc to exit
Build CoreOrdered learning track

Servlet Container Integration

Servlet Integration with Tomcat and Jetty

Memahami bagaimana Jersey/Jakarta REST berjalan di dalam Servlet container, termasuk connector, Servlet lifecycle, filter chain, mapping, async processing, classloading, proxy metadata, Tomcat, Jetty, startup, shutdown, dan production debugging.

20 min read3837 words
PrevNext
Lesson 1350 lesson track10–27 Build Core
#servlet#tomcat#jetty#jersey-servlet+4 more

Part 013 — Servlet Integration with Tomcat and Jetty

Ketika Jersey dijalankan di Tomcat atau Jetty, request terlebih dahulu melewati connector, container dispatch, web application, dan Servlet filter chain sebelum masuk ke Jersey. Servlet container memiliki network-facing dan web-application lifecycle; Jersey memiliki Jakarta REST application model. Memahami boundary ini mencegah tuning, debugging, dan shutdown dilakukan pada layer yang salah.

Daftar Isi

  1. Target kompetensi
  2. Scope dan baseline
  3. Terminology map
  4. Mental model: connector, container, Jersey, resource
  5. Standard versus implementation-specific boundary
  6. Deployment models
  7. WAR anatomy dan dependency placement
  8. ServletContext lifecycle
  9. Servlet, Filter, dan Listener lifecycle
  10. Dispatcher types dan duplicate execution
  11. Servlet dan filter mapping
  12. Path composition dan proxy rewrite
  13. Jersey ServletContainer integration
  14. Jersey as Servlet versus Filter
  15. Bootstrap: web.xml, programmatic, ApplicationPath
  16. Servlet context injection into Jersey
  17. End-to-end request lifecycle
  18. Tomcat architecture dan request pipeline
  19. Tomcat connectors, executors, dan capacity
  20. Tomcat Valve versus Servlet Filter versus JAX-RS Filter
  21. Jetty architecture dan request pipeline
  22. Jetty thread pool, handlers, dan environment alignment
  23. Tomcat versus Jetty
  24. Thread ownership, blocking, dan backpressure
  25. Servlet async processing
  26. Timeout hierarchy dan cancellation
  27. Request, response, dan stream ownership
  28. Sessions, cookies, dan stateless services
  29. Classloading dan duplicate APIs
  30. Authentication, proxy, dan TLS boundaries
  31. Error dispatch dan observability
  32. Startup, readiness, shutdown, dan redeploy
  33. Capacity dan performance review
  34. Architecture patterns dan anti-patterns
  35. Failure-model matrix
  36. Debugging playbook
  37. PR review checklist
  38. Trade-off yang harus dipahami senior engineer
  39. Internal verification checklist
  40. Latihan verifikasi
  41. Ringkasan
  42. Referensi resmi

Target kompetensi

Setelah menyelesaikan part ini, Anda harus mampu:

  • membedakan Servlet specification, Servlet container, Jersey servlet adapter, dan Jakarta REST application;
  • menelusuri request dari load balancer sampai resource method;
  • menjelaskan lifecycle ServletContext, servlet, filter, listener, dan Jersey ApplicationHandler;
  • memetakan context path, servlet mapping, @ApplicationPath, dan resource @Path;
  • membedakan Servlet Filter, Tomcat Valve, Jetty Handler, JAX-RS filter, dan interceptor;
  • memahami dispatcher REQUEST, ASYNC, FORWARD, INCLUDE, dan ERROR;
  • menilai thread ownership, blocking, async processing, timeout, dan cancellation;
  • mendiagnosis mapping conflict, async-not-supported, classloader conflict, proxy mismatch, dan stuck request;
  • membandingkan Tomcat dan Jetty tanpa menganggap implementation detail sebagai standard;
  • memverifikasi runtime internal CSG dari artifact, bootstrap, deployment, logs, dan configuration.

Scope dan baseline

Baseline konseptual:

  • Java 17+;
  • Jakarta Servlet 6.x;
  • Jakarta REST 4.x;
  • Jersey 3.x;
  • Tomcat 10.1/11-style Jakarta deployments;
  • Jetty 12-style deployments;
  • WAR dan embedded runtime.

Versi aktual tetap harus diverifikasi. Minimum Java, level Servlet, nama module, HTTP/2/3 support, virtual-thread integration, dan security defaults berubah antar-release.

Keberadaan dependency berikut bukan bukti runtime production:

jakarta.servlet-api
jersey-container-servlet
org.apache.tomcat.*
org.eclipse.jetty.*

Dependency tersebut dapat berasal dari compile-only API, test harness, embedded development runtime, atau transitive library.


Terminology map

TermArti operasional
Servlet containerRuntime yang mengimplementasikan Jakarta Servlet
web applicationContext terisolasi dengan classloader, filters, servlets, listeners
context pathPrefix untuk memilih web application
servlet mappingRule untuk memilih servlet di dalam web application
connectorNetwork/protocol endpoint yang menerima connection/request
filter chainPipeline portable sebelum dan sesudah servlet
listenerLifecycle/event callback yang dikelola container
dispatcher typeAlasan dispatch: request, async, forward, include, error
Jersey ServletContainerAdapter Servlet/Filter menuju Jersey runtime
Tomcat ValveTomcat-specific container pipeline component
Jetty HandlerJetty-specific request-processing component
async cycleRequest tetap hidup setelah original container thread dikembalikan

Mental model: connector, container, Jersey, resource

flowchart LR Client --> Proxy[LB / Reverse Proxy] Proxy --> Connector[Tomcat or Jetty Connector] Connector --> Container[Servlet Container] Container --> WebApp[Web Application Context] WebApp --> Filters[Servlet Filter Chain] Filters --> Adapter[Jersey ServletContainer] Adapter --> Jersey[Jersey ApplicationHandler] Jersey --> Pipeline[JAX-RS Filters / Providers] Pipeline --> Resource[Resource Method] Resource --> Dependencies[DB / Kafka / HTTP / Redis]

Ownership:

Connector owns protocol-facing connection handling.
Servlet container owns web-application dispatch and component lifecycle.
Jersey adapter converts Servlet request/response into Jakarta REST processing.
Jersey owns resource matching and provider pipeline.
Application owns domain behavior and application-created resources.

Jangan men-debug satu layer seolah-olah layer tersebut memiliki seluruh request lifecycle.


Standard versus implementation-specific boundary

ConcernPortable standardJersey-specificTomcat-specificJetty-specific
Web lifecycleJakarta ServletJersey adapter lifecycleCatalina componentsJetty lifecycle/components
REST modelJakarta RESTResourceConfig, Jersey features
Network connectorTidak portableConnector/ProtocolHandlerConnector/ConnectionFactory
Pre-servlet extensionServlet FilterJersey as FilterValveHandler
Thread configContainer-managed conceptasync bridgeExecutor/connector poolsThreadPool/execution strategy
Access logTidak ada format tunggalJersey log bukan access logAccessLogValveRequestLog
ClassloadingServlet web-app rulesJersey modulesTomcat WebappClassLoaderJetty webapp loader

Rule:

Application code should depend on standard contracts.
Operations config must target the actual runtime implementation.

Deployment models

External container + WAR

flowchart TD Runtime[Tomcat / Jetty Process] --> Deploy[Deploy WAR] Deploy --> Loader[Web-App Classloader] Loader --> Context[ServletContext] Context --> Jersey[Jersey ServletContainer] Jersey --> App[JAX-RS Application]

Container owns process and deployment lifecycle. Shared libraries and hot redeploy increase classloader complexity.

Embedded container + executable JAR

flowchart TD Main[main()] --> Configure[Configure Server / Connector] Configure --> Register[Register Jersey] Register --> Start[Start Runtime] Start --> Ready[Readiness] Ready --> Stop[Drain and Stop]

Application owns bootstrap, security limits, access logs, health, shutdown, and cleanup.

Internal platform wrapper

A company runtime library may hide Tomcat/Jetty and expose only standardized settings. Verify the underlying runtime and lifecycle owner rather than assuming wrapper semantics.


WAR anatomy dan dependency placement

Typical archive:

service.war
├── META-INF/
├── WEB-INF/
│   ├── web.xml
│   ├── classes/
│   └── lib/
└── static-resources/

Review questions:

  • Is jakarta.servlet-api bundled when external container provides it?
  • Is Jersey provided by runtime or application?
  • Are multiple JSON providers bundled?
  • Is the JDBC driver server-wide or application-local?
  • Are environment secrets accidentally packaged?
  • Is static content exposed under the default servlet?

Useful evidence:

mvn -q dependency:tree
jar tf target/service.war | sort
unzip -l target/service.war

ServletContext lifecycle

stateDiagram-v2 [*] --> Creating Creating --> Initializing Initializing --> Available Available --> Stopping Stopping --> Destroying Destroying --> [*]

Container creates one context per deployed web application, establishes classloader/configuration, initializes listeners/filters/servlets, then destroys them during stop or undeploy.

Application invariants:

  • do not retain ServletContext or web-app classes from parent/global static state;
  • close application-owned clients and executors;
  • clear ThreadLocal state;
  • do not close container-owned resources;
  • fail startup when mandatory invariants are invalid.
@WebListener
public final class ApplicationLifecycleListener
        implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent event) {
        // Validate mandatory configuration.
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        // Stop application-owned resources in dependency order.
    }
}

Servlet, Filter, dan Listener lifecycle

Servlet

construct → init(ServletConfig) → service(...) concurrently → destroy()

A servlet instance normally handles many requests concurrently. Request-specific mutable data must not live in instance fields.

Filter

construct → init(FilterConfig) → doFilter(...) concurrently → destroy()

A filter may inspect/wrap/reject, call chain.doFilter() once, then perform post-processing.

Listener

Listeners observe context, request, session, and attribute lifecycle. Keep them lightweight and infrastructure-oriented; do not hide domain workflows inside callbacks.

Unsafe pattern:

public final class UnsafeFilter implements Filter {
    private String currentTenant; // shared across requests
}

Safe request state belongs in method locals, request attributes, or a properly managed request scope.


Dispatcher types dan duplicate execution

DispatcherMeaning
REQUESTInitial client dispatch
ASYNCRedispatch from AsyncContext
FORWARDServer-side forward
INCLUDEIncluded resource
ERRORContainer error dispatch
@WebFilter(
    urlPatterns = "/*",
    dispatcherTypes = {
        DispatcherType.REQUEST,
        DispatcherType.ASYNC,
        DispatcherType.ERROR
    },
    asyncSupported = true
)
public final class CorrelationFilter implements Filter {
}

A single logical request can traverse a filter multiple times. Instrumentation must be dispatcher-aware and idempotent.

Failure examples:

  • duplicate spans;
  • correlation ID overwritten;
  • authorization run twice with different context;
  • duplicate headers;
  • metrics counted per dispatch rather than per logical request.

Servlet dan filter mapping

Common servlet mappings:

MappingExample
exact/health
path/api/*
extension*.action
default/

Filter mapping also includes servlet name and dispatcher types.

<servlet-mapping>
  <servlet-name>Jersey</servlet-name>
  <url-pattern>/api/*</url-pattern>
</servlet-mapping>

Mapping questions:

  • Does an exact servlet mapping win before Jersey?
  • Does the default servlet serve static content under the same path?
  • Is Jersey mapped as / or /*?
  • Is an error dispatch routed back through the same filter chain?
  • Is programmatic registration duplicating descriptor registration?

Path composition dan proxy rewrite

Final external URI may involve:

gateway route
+ rewrite
+ web context path
+ servlet mapping
+ @ApplicationPath
+ resource @Path
+ method @Path

Inspect actual fields:

request.getRequestURI();
request.getContextPath();
request.getServletPath();
request.getPathInfo();
request.getDispatcherType();

Do not infer path from source annotations alone. Gateway and ingress rewrites may remove or add prefixes.

A 404 can originate from:

gateway → virtual host → context → servlet mapping
→ Jersey application path → resource matching

Jersey ServletContainer integration

Jersey ServletContainer can act as Servlet or Filter. Conceptually it:

  1. receives Servlet initialization;
  2. resolves Application/ResourceConfig;
  3. builds the Jersey application model;
  4. adapts Servlet request/response;
  5. exposes supported Servlet objects through @Context;
  6. invokes Jersey matching/filter/provider/resource pipeline;
  7. participates in async completion;
  8. shuts down Jersey components when container stops.
sequenceDiagram participant SC as Servlet Container participant JS as Jersey ServletContainer participant AH as ApplicationHandler participant R as Resource SC->>JS: init(config) JS->>AH: build application SC->>JS: service/doFilter JS->>AH: handle request AH->>R: invoke matched method R-->>AH: result AH-->>JS: response JS-->>SC: commit/complete

Jersey as Servlet versus Filter

Servlet modeFilter mode
Jersey owns a clear mapped API pathJersey overlays an existing web application
Simpler routing and ownershipFlexible mixed legacy/static content
Fewer forwarding ambiguitiesFilter order and fallback behavior are critical
Natural default for API-only serviceUse only when integration requires it

Filter mode risks:

  • shadowing static resources or other servlets;
  • forwarding-on-404 surprises;
  • wrong filter context path;
  • inconsistent async support;
  • ambiguous ownership of response.

Prefer Servlet mode when Jersey owns a dedicated API namespace.


Bootstrap: web.xml, programmatic, ApplicationPath

Descriptor

<servlet>
  <servlet-name>QuoteOrderApi</servlet-name>
  <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
  <init-param>
    <param-name>jakarta.ws.rs.Application</param-name>
    <param-value>com.example.api.QuoteOrderApplication</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
  <async-supported>true</async-supported>
</servlet>
<servlet-mapping>
  <servlet-name>QuoteOrderApi</servlet-name>
  <url-pattern>/api/*</url-pattern>
</servlet-mapping>

Annotation

@ApplicationPath("/api")
public final class QuoteOrderApplication extends Application {
}

Programmatic registration

ServletRegistration.Dynamic jersey = context.addServlet(
    "QuoteOrderApi",
    new ServletContainer(new QuoteOrderResourceConfig()));
jersey.setLoadOnStartup(1);
jersey.setAsyncSupported(true);
jersey.addMapping("/api/*");

Select one explicit bootstrap model. Mixing descriptor, scanning, annotation discovery, and programmatic registration creates duplicate or environment-dependent behavior.


Servlet context injection into Jersey

Servlet deployment can expose:

  • HttpServletRequest;
  • HttpServletResponse;
  • ServletContext;
  • ServletConfig or FilterConfig;
  • Jersey WebConfig.
@Path("/diagnostics")
public final class DiagnosticsResource {
    @Context
    HttpServletRequest request;

    @GET
    public Map<String, String> inspect() {
        return Map.of(
            "contextPath", request.getContextPath(),
            "servletPath", request.getServletPath(),
            "dispatcher", request.getDispatcherType().name());
    }
}

Prefer portable JAX-RS abstractions where sufficient:

  • UriInfo;
  • HttpHeaders;
  • SecurityContext;
  • Request;
  • Configuration.

Servlet-specific injection reduces runtime portability and requires Servlet-based tests.


End-to-end request lifecycle

sequenceDiagram participant C as Client participant P as Proxy participant CN as Connector participant SF as Servlet Filters participant JS as Jersey Adapter participant JP as JAX-RS Pipeline participant R as Resource participant D as Dependency C->>P: HTTP request P->>CN: proxied request CN->>CN: parse protocol and limits CN->>SF: mapped web-app dispatch SF->>JS: chain.doFilter JS->>JP: Jersey request JP->>R: invoke R->>D: DB/Kafka/HTTP D-->>R: result/failure R-->>JP: response/exception JP-->>JS: serialized response JS-->>SF: return or async SF-->>CN: response commit CN-->>C: HTTP response

A resource breakpoint not hit does not prove request never reached the process. Rejection may happen at TLS, connector parsing, limits, host/context mapping, filters, Jersey pre-matching, or entity decoding.


Tomcat architecture dan request pipeline

Conceptual hierarchy:

flowchart TD Server --> Service Service --> Connector Service --> Engine Engine --> Host Host --> Context Context --> Wrapper Engine -.-> Valve Host -.-> Valve Context -.-> Valve

Simplified request path:

socket → endpoint/protocol handler → adapter
→ Engine → Host → Context → Wrapper pipelines
→ Servlet filter chain → Jersey servlet

Key Tomcat concepts are implementation-specific:

  • Connector;
  • Engine;
  • Host;
  • Context;
  • Wrapper;
  • Valve;
  • shared Executor.

Debug by locating the first layer with evidence of the request.


Tomcat connectors, executors, dan capacity

Connector settings influence:

  • address/port;
  • HTTP protocol;
  • TLS;
  • keep-alive;
  • connection/request limits;
  • header/body limits;
  • compression;
  • proxy scheme/port;
  • timeout;
  • executor/thread integration.

Important distinction:

accepted connections
≠ active request threads
≠ queued requests
≠ DB connections
≠ downstream concurrency

Increasing worker threads while DB pool remains small can convert fast rejection into slow timeout amplification.

Map:

connector → executor → web apps → endpoint cost
→ DB/client pools → CPU/memory/network

Tomcat Valve versus Servlet Filter versus JAX-RS Filter

LayerPortabilityTypical purpose
Tomcat ValveTomcat-onlyaccess log, remote address normalization, container security
Servlet FilterServlet-portableweb-app auth bridge, request wrappers, common headers
JAX-RS FilterJakarta REST-portableAPI-specific auth, route metadata, resource-bound policies

Place logic at the narrowest correct boundary.

Anti-pattern:

correlation ID generated independently in Valve,
Servlet Filter, and JAX-RS Filter

This causes competing IDs and fragmented traces.


Jetty architecture dan request pipeline

Jetty core model:

Server + ThreadPool + Connectors + Handlers
flowchart TD Server --> ThreadPool Server --> Connector Connector --> Protocols[Connection Factories] Server --> Handlers[Handler Tree] Handlers --> Context[Context / WebApp] Context --> ServletEnv[Servlet Environment] ServletEnv --> Jersey[Jersey ServletContainer]

Jetty 12 supports core Handler-based applications and multiple Jakarta EE web environments. Presence of Jetty does not prove Servlet is used.

Simplified Servlet path:

connection → protocol parser → execution strategy/thread pool
→ handler tree → context → servlet filters
→ Jersey adapter → JAX-RS

Jetty thread pool, handlers, dan environment alignment

Operational dimensions:

  • connector and protocol factories;
  • handler tree;
  • context mapping;
  • QueuedThreadPool or configured execution model;
  • reserved capacity;
  • request logging;
  • forwarded-request customization;
  • stop timeout;
  • Servlet/Jakarta environment modules.

Avoid mixing Jetty ee8, ee9, ee10, or ee11 artifacts. Align:

Java version
Jetty major/minor
Jakarta Servlet level
Jersey Servlet module
Jakarta namespace

Do not tune Jetty threads using Tomcat terminology or assumptions.


Tomcat versus Jetty

DimensionTomcatJetty
Core identityServlet/JSP containerEmbeddable server with handler and Servlet environments
HierarchyEngine/Host/Context/WrapperServer/Connector/Handler/Context
Pre-web extensionValveHandler
Embedded supportSupportedStrongly compositional
Thread model termsconnector executor/protocolthread pool/execution strategy
Non-Servlet APImostly container internalsfirst-class core Handler API

Do not claim one is universally faster. Compare using:

  • actual workload;
  • version;
  • TLS/protocol needs;
  • blocking profile;
  • deployment model;
  • operational tooling;
  • support and patching model.

Thread ownership, blocking, dan backpressure

Potential waiting points:

flowchart LR Worker --> Lock Worker --> DBPool --> DB Worker --> HTTPPool --> Remote Worker --> KafkaAck Worker --> ClientWrite

Invariant:

request concurrency must be bounded by constrained dependencies,
not only by container maximum threads

Example:

200 request threads
20 DB connections
unbounded incoming queue
5s DB acquisition timeout

This can produce 180 waiting threads, rising p99, retry storms, and starvation of lightweight endpoints.

Controls:

  • bounded queues;
  • bulkheads;
  • route-level concurrency limits;
  • aligned pool sizes;
  • timeout budgets;
  • load shedding;
  • cancellation;
  • separate management capacity where justified.

Servlet async processing

sequenceDiagram participant T as Container Thread participant S as Servlet/Filter participant A as AsyncContext participant E as Completion Source T->>S: dispatch S->>A: startAsync() S-->>T: return thread E-->>A: result A->>S: dispatch/write S->>A: complete()

Rules:

  • every filter/servlet in path must support async;
  • async request has timeout lifecycle;
  • ASYNC redispatch may run filters again;
  • completion/error/timeout cleanup is mandatory;
  • async releases a thread but retains connection/request state;
  • moving blocking work to an unbounded executor is not backpressure.

Questions:

  • Which executor completes the operation?
  • Is MDC/OpenTelemetry/security context propagated?
  • Can downstream work be cancelled?
  • Is completion exactly once?
  • What happens on client disconnect?

Timeout hierarchy dan cancellation

Typical hierarchy:

client
→ gateway
→ load balancer idle timeout
→ connector
→ Servlet async
→ JAX-RS/application deadline
→ HTTP client
→ DB statement/lock timeout

Bad:

gateway timeout 30s
downstream timeout 60s

The client receives an error while server work continues, potentially committing a mutation that gets retried.

Collect timestamps for:

  • request accepted;
  • filter/Jersey/resource start;
  • downstream start/end;
  • response commit;
  • gateway timeout;
  • cancellation;
  • async complete.

Network cancellation and business cancellation are different contracts.


Request, response, dan stream ownership

Container owns HttpServletRequest and HttpServletResponse lifecycle.

Do not:

  • store request/response in singleton fields;
  • access after sync completion or async completion;
  • write after response committed;
  • close container-owned streams contrary to framework contract;
  • use request objects from arbitrary threads without async contract.

Response can commit when buffer fills or stream is flushed. After commit:

  • status and headers may no longer change;
  • exception mapper/error page may not replace response;
  • client may receive partial body.

Telemetry should distinguish pre-commit failure, post-commit failure, and client disconnect.


Sessions, cookies, dan stateless services

Servlet sessions are valid but should be intentional.

Distributed risks:

  • sticky sessions;
  • replication overhead;
  • serialization incompatibility;
  • memory growth;
  • stale state;
  • failover complexity;
  • coupling auth and mutable server state.

Mutable quote/order state generally belongs in durable domain storage, workflow state, event log, or explicit cache—not implicit HttpSession.

Cookie review:

  • Secure;
  • HttpOnly;
  • SameSite;
  • path/domain;
  • expiry/rotation;
  • proxy TLS awareness;
  • signing/encryption;
  • logout/invalidation.

Classloading dan duplicate APIs

Conceptual hierarchy:

flowchart TD JVM[JVM Platform] --> Container[Container Libraries] Container --> WebApp[Web-App Classloader] WebApp --> Classes[WEB-INF/classes] WebApp --> Lib[WEB-INF/lib]

Common failures:

  • ClassNotFoundException;
  • NoClassDefFoundError;
  • NoSuchMethodError;
  • AbstractMethodError;
  • LinkageError;
  • same-name ClassCastException;
  • javax.*/jakarta.* mismatch.

Invariant:

class identity = class name + defining classloader

Verify whether Servlet/Jersey/CDI/JSON libraries are container-provided or application-local. Do not bundle duplicate API/implementation sets without an explicit compatibility plan.


Authentication, proxy, dan TLS boundaries

Possible security layers:

gateway authentication
container security
Servlet filter
JAX-RS filter
domain authorization
database tenant predicate

Trusted proxy metadata affects:

  • scheme;
  • host;
  • port;
  • client IP;
  • secure cookies;
  • generated links;
  • OAuth redirects;
  • audit;
  • rate limits.
flowchart LR Client --> TrustedProxy TrustedProxy -->|strip/replace forwarding headers| Container Container -->|normalized metadata| Jersey

Never trust X-Forwarded-*, user, tenant, or certificate headers from arbitrary clients. Restrict direct access and trust only controlled proxy ranges.


Error dispatch dan observability

Errors can be owned by:

  • connector/protocol parser;
  • container error dispatch;
  • Servlet filter;
  • Jersey ExceptionMapper;
  • response writer;
  • gateway timeout.

Evidence sources answer different questions:

SourceQuestion
LB/container access logDid HTTP reach/leave this boundary?
Servlet filter logDid request enter web app?
Jersey tracingDid matching/provider/resource execute?
application logWhich domain operation occurred?
distributed traceWhich dependency caused latency/failure?
metricsIs the problem systemic?

Do not expose stack traces. Preserve trace/correlation IDs and classify post-commit failures separately.


Startup, readiness, shutdown, dan redeploy

Startup:

flowchart TD JVM --> Connector --> Context --> Components Components --> JerseyModel[Jersey Model Validation] JerseyModel --> Resources[Critical Resources] Resources --> Ready

Port listening is not readiness.

Graceful shutdown:

flowchart TD Signal --> NotReady --> RemoveTraffic RemoveTraffic --> Drain Drain --> StopAsync StopAsync --> CloseAppResources CloseAppResources --> StopContainer

Close dependency pools only after in-flight users stop.

Redeploy leak sources:

  • non-daemon threads;
  • executors/schedulers;
  • ThreadLocal;
  • HTTP/Kafka clients;
  • JDBC/logging/MBean registries;
  • parent-classloader static caches;
  • shutdown hooks per deployment.

Kubernetes process replacement reduces hot-redeploy frequency but does not remove cleanup requirements.


Capacity dan performance review

Key resources:

connections
container threads
request queue
heap/native buffers
DB/client pools
CPU quota
file descriptors
network bandwidth

Little's Law intuition:

concurrency ≈ throughput × latency

At 100 requests/s and 2 seconds latency, approximately 200 concurrent requests are retained somewhere.

Review:

  • p95/p99 service and queue time;
  • compression CPU;
  • slow-client writes;
  • request buffering;
  • async in-flight count;
  • DB/client pool alignment;
  • CPU throttling;
  • access-log overhead;
  • file-descriptor limits.

Architecture patterns dan anti-patterns

Patterns

  1. Explicit Jersey servlet mapping with explicit Application/ResourceConfig.
  2. Embedded single-service container with source-controlled bootstrap.
  3. Internal runtime wrapper with observable effective configuration.
  4. Request-boundary normalization for proxy metadata, identity, and correlation.
  5. Dedicated management capacity where business saturation must not hide health.

Anti-patterns

  • treating Jersey as connector owner;
  • multiple implicit bootstraps;
  • mutable request state in component fields;
  • unbounded worker threads;
  • trusting all forwarded headers;
  • duplicate logic across Valve, Servlet Filter, and JAX-RS Filter;
  • async without cancellation;
  • readiness equal to open port;
  • closing container-owned resources;
  • relying on defaults as architecture decisions.

Failure-model matrix

FailureLayerSymptomsEvidenceMitigation
bind/TLS failureconnectorstartup/no app logsconnector/LB logsfail fast, correct network/certs
header/body limitconnector/filter4xx before Jerseyaccess/error logexplicit documented limits
wrong context/mappingcontainer404/default contentmapping/path fieldsone explicit path model
filter blocks chainServletempty/partial responsefilter traceintentional response or chain
duplicate filter dispatchasync/errorduplicate spans/headersdispatcher typeidempotent dispatch handling
async unsupportedchain configIllegalStateExceptionstack/configasync support end-to-end
Jersey bootstrap failureJerseydeploy/first request failsmodel/startup logseager validation
classloader collisionpackaginglinkage errorsdependency/class sourcealign scopes/versions
worker saturationcontainerlatency/timeoutsthread/pool metricsbounds and bulkheads
proxy mismatchboundarywrong scheme/IP/cookiemetadata comparisontrusted normalization
post-commit failureresponsepartial body/resetcommitted/write logsprevalidate, stream protocol
early pool closeshutdownin-flight 5xxshutdown timelinereadiness-first drain
redeploy leakclassloaderduplicate jobs/metaspacethread/heap dumpcleanup owned resources

Debugging playbook

External 404

  1. Confirm DNS/LB target.
  2. Check LB and container access logs.
  3. Record external/internal path rewrite.
  4. Inspect context path and servlet mapping.
  5. Inspect application/resource paths.
  6. Enable safe Jersey tracing in a controlled environment.
  7. Add real Servlet-deployment regression test.

Hanging requests

Collect together:

  • thread dump;
  • connector/thread-pool metrics;
  • DB/client pool metrics;
  • CPU throttling;
  • lock data;
  • async request count;
  • downstream latency;
  • timeout timeline.

Works embedded, fails external

Compare classloader, API versions, archive contents, scanning, mappings, managed resources, proxy path, and CDI/HK2 integration.

Shutdown 5xx

Build timeline from termination signal through readiness false, endpoint removal, last new request, resource closure, and forced kill. Fix ordering rather than only increasing grace period.


PR review checklist

Packaging/runtime

  • Runtime and deployment model are explicit.
  • Java/Servlet/Jersey/container versions align.
  • API scope and archive contents are correct.
  • Duplicate implementations are absent.

Bootstrap/routing

  • One Jersey bootstrap model.
  • Context, servlet, application, and resource paths documented.
  • Mapping tested after proxy rewrite.
  • Startup validation is eager enough.

Filters/async

  • Filter order and dispatcher types are deterministic.
  • Filters are thread-safe.
  • Async support is end-to-end.
  • Context propagation and completion-once behavior are tested.
  • Cross-layer logic is not duplicated.

Security/operations

  • Trusted proxy policy exists.
  • Principal/role bridge is defined.
  • Readiness and shutdown ordering are tested.
  • Access logs, pool metrics, and traces exist.
  • Sensitive headers/bodies are redacted.
  • Client disconnect and post-commit failures are classified.

Trade-off yang harus dipahami senior engineer

DecisionOption AOption B
PackagingExternal WAR: centralized runtime, shared-library riskEmbedded: app-owned bootstrap, stronger isolation
Jersey integrationServlet: clear API namespaceFilter: legacy/mixed-content flexibility
ProcessingSync: simpler lifecycleAsync: releases thread, adds completion complexity
LibrariesContainer-wide: centralized patching, broad blast radiusApp-local: independent versioning, larger artifact
CapacityMore threads: temporary utilizationBackpressure: controlled overload and dependency protection

Principal-level question:

Di mana overload ditolak, siapa pemilik queue, dan apakah unit tersebut benar-benar bounded?


Internal verification checklist

Runtime and artifact

  • Tomcat, Jetty, GlassFish, Grizzly, atau wrapper lain?
  • Exact versions and Java vendor.
  • WAR versus embedded JAR.
  • Final archive/image inventory.
  • Main class/startup command.
  • Runtime owner.

Bootstrap and routing

  • web.xml, web fragments, initializers, listeners.
  • @ApplicationPath and ResourceConfig.
  • Package scanning.
  • Context/servlet/filter mapping.
  • Gateway and ingress rewrites.
  • Static/default servlet interaction.

Container specifics

  • Tomcat connectors/executors/Valves/access log, if used.
  • Jetty connectors/handlers/thread pool/environment modules, if used.
  • Classloader delegation and shared libraries.
  • Forwarded-header normalization.
  • Request/header/body/timeout limits.

Lifecycle and capacity

  • Filter order, dispatcher types, async flags.
  • Thread pools and queues.
  • DB/HTTP-client pools.
  • Readiness gate.
  • Shutdown/drain sequence.
  • Redeploy or process-replacement policy.
  • Saturation dashboards and runbooks.

Evidence sources

  • dependency tree;
  • archive and image contents;
  • server config;
  • Kubernetes manifests;
  • startup/access logs;
  • thread dumps;
  • historical incidents/PRs;
  • onboarding discussions with platform owners.

Latihan verifikasi

  1. Prove the actual runtime from process, image, logs, ports, and dependencies.
  2. Decompose one external endpoint into every path prefix and rewrite.
  3. Deploy Jersey as Servlet and Filter; compare fallback and 404 behavior.
  4. Trace REQUEST, ASYNC, and ERROR filter invocations.
  5. Reproduce asyncSupported=false failure.
  6. Saturate a bounded dependency and correlate thread/queue metrics.
  7. Simulate TLS termination and trusted forwarded headers.
  8. Reproduce classloader/API collision.
  9. Test readiness-first graceful shutdown during a mutation.
  10. Create and fix a redeploy thread leak.
  11. Run the same Jersey tests on Tomcat and Jetty.
  12. Correlate LB, container, Servlet, Jersey, application, and trace evidence.

Ringkasan

Mental model:

Tomcat or Jetty receives and dispatches HTTP through a Servlet runtime.
Jersey ServletContainer adapts that request into Jakarta REST processing.
Servlet and Jersey lifecycles interact but have distinct owners.

Key invariants:

  1. Jersey is not the connector in Servlet deployment.
  2. Context path, servlet mapping, application path, and resource path are separate.
  3. Servlet Filter, Tomcat Valve, Jetty Handler, and JAX-RS Filter are different pipelines.
  4. Container components are concurrent.
  5. Async releases a thread but does not automatically cancel work.
  6. Forwarded metadata is trusted only from controlled proxies.
  7. Connector threads and downstream pools must be capacity-aligned.
  8. Response commitment limits error recovery.
  9. Startup readiness and graceful shutdown are lifecycle contracts.
  10. Runtime details must be proven from executable evidence, not dependency names.

Referensi resmi

Lesson Recap

You just completed lesson 13 in build core. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.