Servlet Container Runtime
Servlet Container Runtime Tomcat and Jetty
Cara JAX-RS berjalan di atas Servlet container: Servlet lifecycle, filter chain, servlet mapping, context path, request thread, deployment unit, dan debugging runtime
Part 017 — Servlet Container Runtime: Tomcat and Jetty
Fokus part ini: memahami bagaimana service JAX-RS/Jakarta REST berjalan ketika ditempatkan di atas Servlet container seperti Tomcat atau Jetty.
Part sebelumnya membahas CDI/Jakarta Inject dan bagaimana object dapat dibuat oleh runtime DI.
Part ini turun satu level ke runtime HTTP yang menerima socket, membuat request object, menjalankan filter chain, lalu menyerahkan request ke JAX-RS runtime.
Pertanyaan utamanya:
What receives the HTTP request before JAX-RS sees it?
What is a Servlet container responsible for?
How does JAX-RS attach itself to a Servlet runtime?
Where do Tomcat and Jetty fit?
What is the difference between context path, servlet mapping, and @Path?
Why can a perfectly valid JAX-RS resource still return 404?
Untuk konteks CSG Quote & Order, jangan langsung mengasumsikan Tomcat atau Jetty dipakai. Gunakan part ini sebagai model verifikasi runtime jika codebase menunjukkan artifact WAR, servlet initializer, embedded server, atau dependency Servlet container.
1. Mental Model: Servlet Container as the HTTP Runtime
JAX-RS resource method bukan komponen pertama yang menerima request.
Dalam deployment berbasis Servlet, request melewati beberapa layer terlebih dahulu:
Client
-> DNS / Load Balancer / API Gateway / Ingress
-> Servlet container connector
-> Servlet container request parser
-> Servlet filter chain
-> JAX-RS servlet/filter integration
-> JAX-RS request matching
-> JAX-RS providers / filters / interceptors
-> resource method
Servlet container bertanggung jawab atas hal-hal seperti:
network connector
HTTP parsing
request and response object lifecycle
thread dispatch
servlet registration
filter chain execution
context path handling
session infrastructure if enabled
static resource handling if configured
web application lifecycle
JAX-RS bertanggung jawab atas:
resource matching
annotation-driven routing
parameter binding
entity conversion
JAX-RS provider/filter/interceptor execution
ExceptionMapper handling
Response object construction
Keduanya berada di layer berbeda.
Kesalahan senior yang sering terjadi adalah mendiagnosis semua 404, 405, timeout, atau header mutation sebagai masalah JAX-RS, padahal sebagian besar bisa berasal dari Servlet mapping, context path, proxy rewrite, container filter, atau connector limit.
2. Servlet Container vs JAX-RS Runtime
Servlet API menyediakan abstraction HTTP level rendah:
HttpServletRequest
HttpServletResponse
Filter
FilterChain
Servlet
ServletContext
ServletConfig
JAX-RS menyediakan programming model yang lebih tinggi:
@Path
@GET
@POST
@Consumes
@Produces
@Context
ContainerRequestFilter
ContainerResponseFilter
ExceptionMapper
MessageBodyReader
MessageBodyWriter
Relasinya:
Servlet container owns the HTTP request lifecycle.
JAX-RS runtime adapts that lifecycle into resource method invocation.
Dalam Jersey di Servlet environment, biasanya ada integrasi melalui ServletContainer atau initializer.
Contoh konseptual:
Tomcat/Jetty receives request
Tomcat/Jetty invokes Jersey ServletContainer
Jersey creates JAX-RS request context
Jersey matches resource
Jersey invokes resource method
Jersey writes response through Servlet response
Yang standar:
Servlet API concepts
Jakarta REST/JAX-RS annotations and extension points
Yang implementation-specific:
Jersey ServletContainer
Jersey ResourceConfig integration
Tomcat connector behavior
Jetty handler/server model
Embedded server bootstrap code
3. Deployment Unit: WAR, Executable JAR, or Embedded Server
Service Java enterprise biasanya berjalan dengan salah satu model berikut.
3.1 WAR deployed to external container
application.war
-> deployed into Tomcat/Jetty/application server
-> container owns process lifecycle
-> app owns web application lifecycle
Ciri-ciri codebase:
src/main/webapp
WEB-INF/web.xml
packaging war in pom.xml
provided-scope servlet dependency
external container config
3.2 Executable JAR with embedded container
java -jar service.jar
-> main method starts embedded Tomcat/Jetty/Grizzly/etc.
-> application owns process and container lifecycle
Ciri-ciri codebase:
main class
embedded server dependency
programmatic server bootstrap
container port configured by app config
Docker CMD runs java -jar
3.3 Framework-managed executable runtime
Beberapa framework menyembunyikan detail container. Untuk seri ini, jangan mengasumsikan framework tertentu kecuali terlihat di dependency.
Ciri-ciri umum:
fat jar / shaded jar / layered jar
framework plugin in Maven
runtime config files
server selected by framework
Internal verification checklist
[ ] Apakah packaging Maven `war`, `jar`, atau custom?
[ ] Apakah ada `WEB-INF/web.xml`?
[ ] Apakah ada class yang extend `Application` atau `ResourceConfig`?
[ ] Apakah ada `main()` yang start embedded server?
[ ] Apakah Dockerfile menjalankan WAR ke external container atau menjalankan JAR langsung?
[ ] Apakah deployment manifest menyebut container image berbasis Tomcat/Jetty?
[ ] Apakah startup logs menyebut Tomcat, Jetty, Jersey ServletContainer, atau Servlet initializer?
4. Servlet Lifecycle
Servlet container memiliki lifecycle sendiri.
Simplified lifecycle:
process starts
-> container starts connectors
-> web application context is initialized
-> listeners are called
-> filters are initialized
-> servlets are initialized
-> application is ready to receive requests
-> requests are dispatched
-> shutdown begins
-> stop accepting new requests
-> destroy servlets and filters
-> release web application resources
-> process exits
JAX-RS bootstrap biasanya terjadi saat Servlet/Jersey component diinisialisasi.
Implikasi production:
startup failure may happen before any endpoint exists
missing provider may fail at boot or first request depending on runtime
heavy initialization in servlet init can delay readiness
unclean shutdown can interrupt in-flight requests
classloader leaks can appear after redeploy in external container
Senior engineer harus bertanya:
What is initialized at startup?
What is initialized lazily on first request?
What happens during shutdown?
Are in-flight requests drained or killed?
5. Context Path, Servlet Mapping, and JAX-RS @Path
Tiga konsep ini sering tertukar.
context path = web application base path
servlet mapping = path segment routed to the JAX-RS servlet/filter
@Path = JAX-RS resource path inside JAX-RS runtime
Contoh:
context path: /quote-order
servlet mapping: /api/*
resource @Path: /quotes
method @Path: /{quoteId}
Final URL:
/quote-order/api/quotes/{quoteId}
Kalau request ke /quotes/{quoteId} gagal, belum tentu resource tidak ada.
Kemungkinan:
wrong context path
wrong servlet mapping
proxy strips or adds prefix
Ingress rewrite mismatch
API gateway base path mismatch
JAX-RS @ApplicationPath mismatch
resource @Path mismatch
Debugging 404 path issue
Gunakan urutan ini:
1. Confirm external URL seen by client.
2. Confirm gateway/ingress rewrite.
3. Confirm service context path.
4. Confirm servlet mapping or @ApplicationPath.
5. Confirm JAX-RS resource @Path.
6. Confirm method-level @Path.
7. Confirm HTTP method.
8. Confirm media type constraints.
Jangan mulai dari resource method saja.
6. web.xml, Servlet Initializer, and Annotation Bootstrap
Servlet app bisa dikonfigurasi melalui beberapa mekanisme.
6.1 web.xml
Contoh konseptual:
<servlet>
<servlet-name>JerseyServlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jakarta.ws.rs.Application</param-name>
<param-value>com.company.api.QuoteOrderApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>JerseyServlet</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
Interpretasi:
Servlet container routes /api/* to Jersey's ServletContainer.
Jersey then uses QuoteOrderApplication to register resources/providers.
6.2 Programmatic initializer
Modern apps bisa memakai initializer:
public class AppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
// register servlet, filters, listeners programmatically
}
}
Atau API Servlet langsung:
ServletRegistration.Dynamic servlet = servletContext.addServlet(
"jersey",
new ServletContainer(new QuoteOrderResourceConfig())
);
servlet.addMapping("/api/*");
6.3 Annotation-based bootstrap
Beberapa class memakai annotation seperti:
@ApplicationPath("/api")
public class QuoteOrderApplication extends Application {
}
Tetapi effect akhirnya tetap sama: perlu ada mapping dari container ke JAX-RS runtime.
Internal verification checklist
[ ] Apakah mapping berasal dari `web.xml`?
[ ] Apakah mapping berasal dari initializer Java?
[ ] Apakah ada `@ApplicationPath`?
[ ] Apakah external path sama dengan internal servlet path?
[ ] Apakah gateway/ingress mengubah path sebelum masuk container?
7. Servlet Filter Chain vs JAX-RS Filters
Servlet filter dan JAX-RS filter bukan hal yang sama.
Servlet filter berjalan di layer container:
public class SecurityServletFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
chain.doFilter(req, res);
}
}
JAX-RS filter berjalan di layer JAX-RS:
@Provider
public class SecurityJaxrsFilter implements ContainerRequestFilter {
public void filter(ContainerRequestContext ctx) {
// JAX-RS request context
}
}
Urutan konseptual:
Servlet connector
-> Servlet filter A
-> Servlet filter B
-> Jersey ServletContainer
-> JAX-RS ContainerRequestFilter
-> request matching
-> resource method
-> JAX-RS ContainerResponseFilter
-> Servlet filter response phase
Implikasi:
Servlet filter can block request before JAX-RS sees it.
Servlet filter can mutate headers before JAX-RS.
JAX-RS filter cannot fix path mapping failure outside JAX-RS.
Servlet filter may consume request body before MessageBodyReader runs.
Failure mode umum:
CORS handled twice
auth handled at wrong layer
body consumed by logging filter
correlation ID generated twice
response header overwritten by container filter
PR review question:
Should this behavior live in Servlet filter, JAX-RS filter, gateway, or application service code?
8. Request Thread Model
Dalam Servlet container tradisional, request biasanya diproses oleh thread dari container worker pool.
Simplified model:
network connector accepts request
-> worker thread assigned
-> servlet filter chain runs on worker thread
-> JAX-RS resource method runs on worker thread
-> response written
-> worker thread returned to pool
Masalah terjadi ketika resource method melakukan blocking I/O lama:
DB query slow
HTTP downstream slow
file upload/download slow
lock wait
synchronous Kafka call wait
external system timeout too high
Akibatnya:
worker threads occupied
queue grows
latency increases
timeouts happen
retries amplify load
service appears down while process still alive
Tomcat/Jetty worker thread nuance
Tomcat dan Jetty memiliki detail internal berbeda, tetapi model reasoning-nya sama:
finite request-processing capacity
finite connection capacity
finite queue/backlog capacity
blocking work consumes request-processing capacity
Senior engineer tidak harus hafal semua class internal container, tetapi harus tahu titik verifikasinya:
max threads
min threads
accept queue
connection timeout
keep-alive behavior
request header size limit
max request body size if enforced at this layer
9. Tomcat Runtime Model — What to Know
Tomcat adalah Servlet container yang umum dipakai untuk menjalankan web application Java.
Konsep penting:
Connector
Engine
Host
Context
Wrapper/Servlet
Filter chain
Thread pool
Access log
Untuk JAX-RS engineer, yang paling penting:
connector receives HTTP traffic
context path identifies web application
servlet mapping routes to Jersey/JAX-RS integration
thread pool limits request concurrency
filters may run before JAX-RS
container config can reject request before application code
Failure mode yang sering terlihat:
404 because context path/mapping mismatch
413 because body size limit at proxy/container layer
400 because header too large or malformed
connection reset because keep-alive/proxy timeout mismatch
slow response because request threads exhausted
memory leak after redeploy because app classloader retained
Internal verification checklist:
[ ] Apakah container image berbasis Tomcat?
[ ] Apakah aplikasi deploy sebagai WAR?
[ ] Di mana context path dikonfigurasi?
[ ] Apakah ada custom Tomcat connector config?
[ ] Berapa max threads dan queue/backlog?
[ ] Apakah access log aktif dan masuk ke log pipeline?
[ ] Apakah ada custom Servlet filter sebelum Jersey?
10. Jetty Runtime Model — What to Know
Jetty sering dipakai sebagai embedded server atau Servlet container ringan.
Konsep penting:
Server
Connector
Handler
ServletContextHandler
ServletHolder
FilterHolder
QueuedThreadPool
Dalam embedded mode, bootstrap bisa terlihat seperti:
Server server = new Server(8080);
ServletContextHandler context = new ServletContextHandler(server, "/");
context.addServlet(new ServletHolder(new ServletContainer(resourceConfig)), "/api/*");
server.start();
server.join();
Konsekuensi embedded mode:
application code owns server startup
application code owns shutdown behavior
configuration may be hidden in bootstrap class
thread pool may be configured programmatically
container logs may be mixed with application logs
Failure mode yang sering terlihat:
wrong handler order
wrong servlet mapping
thread pool too small
server starts but JAX-RS resources not registered
shutdown does not drain requests
metrics do not include container-level stats
Internal verification checklist:
[ ] Apakah ada dependency Jetty?
[ ] Apakah ada `new Server(...)` di codebase?
[ ] Apakah Jetty dipakai embedded atau external?
[ ] Di mana thread pool dikonfigurasi?
[ ] Apakah lifecycle start/stop dikelola manual?
[ ] Apakah Jersey ServletContainer didaftarkan melalui ServletHolder?
11. Servlet Request and Response Objects
JAX-RS sering menyembunyikan Servlet request/response, tetapi keduanya masih dapat diakses via @Context jika runtime mendukung Servlet integration.
Contoh:
@GET
@Path("/debug")
public Response debug(@Context jakarta.servlet.http.HttpServletRequest request) {
String remoteAddr = request.getRemoteAddr();
return Response.ok(remoteAddr).build();
}
Gunakan dengan hati-hati.
Risiko:
couples resource to Servlet runtime
makes non-Servlet runtime harder
encourages reading low-level mutable state
can bypass clean abstraction
harder to test without servlet mocks
Kapan masuk akal:
need low-level remote address after proxy handling
need servlet-specific auth/session info
need diagnose container-specific behavior
need bridge legacy Servlet code
PR review rule:
Do not inject Servlet APIs into resource methods unless there is a clear runtime-specific reason.
12. Request Body Consumption Risk
Request body pada Servlet/JAX-RS biasanya stream.
Jika layer awal membaca stream dan tidak mengembalikannya, layer berikutnya tidak bisa membaca body.
Anti-pattern:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
String body = new String(req.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
log.info("body={}", body);
chain.doFilter(req, res);
}
Risiko:
JAX-RS MessageBodyReader receives empty stream
large body is buffered into heap
PII leaks into logs
multipart upload breaks
streaming endpoint breaks
Safer approach:
log metadata, not full body
redact sensitive fields
limit body capture size
use request wrapper only if truly needed
avoid body logging for binary/multipart/large payload
Internal verification checklist:
[ ] Apakah ada Servlet filter yang membaca request body?
[ ] Apakah ada request wrapper untuk caching body?
[ ] Apakah ada max captured body size?
[ ] Apakah body logging redacted?
[ ] Apakah streaming/multipart endpoint dikecualikan?
13. Container-Level Limits
Container dapat menolak request sebelum JAX-RS dipanggil.
Contoh limit:
max request header size
max URI length
max form parameter count
max request body size if configured
connection timeout
keep-alive timeout
max concurrent connections
max threads
accept queue/backlog
Symptom:
JAX-RS log tidak muncul
resource method tidak terpanggil
ExceptionMapper tidak menangkap error
access log mungkin ada, application log tidak ada
client melihat 400/413/414/431/503/connection reset
Debugging rule:
If JAX-RS filters do not log the request, investigate gateway, ingress, proxy, and Servlet container before debugging resource code.
14. Access Logs vs Application Logs
Servlet container biasanya dapat menghasilkan access log.
Access log menjawab:
Did the request reach the container?
What method/path/status/latency was observed at container boundary?
What was the remote address or proxy-forwarded address?
How many bytes were sent?
Application log menjawab:
What did business/application code do?
What resource method ran?
What downstream calls happened?
What error was mapped?
Keduanya berbeda.
Dalam debugging path/routing issue, access log sering lebih penting dari application log.
Internal verification checklist:
[ ] Apakah access log aktif?
[ ] Apakah access log masuk centralized logging?
[ ] Apakah access log punya correlation/request ID?
[ ] Apakah path yang tercatat sudah path setelah rewrite atau sebelum rewrite?
[ ] Apakah status access log sama dengan status yang dilihat client?
15. Forwarded Headers and Real Client Identity
Di production, service jarang menerima request langsung dari client.
Biasanya:
client -> CDN/WAF/API gateway -> ingress/load balancer -> service
Akibatnya, request.getRemoteAddr() mungkin menunjuk proxy, bukan client asli.
Header umum:
X-Forwarded-For
X-Forwarded-Proto
X-Forwarded-Host
Forwarded
Risiko:
trusting spoofed forwarded headers
wrong absolute URL generation
wrong audit source IP
wrong redirect scheme http vs https
wrong tenant/routing decision based on host
Rule:
Only trust forwarded headers if they are set or sanitized by trusted infrastructure.
Internal verification checklist:
[ ] Siapa yang menetapkan forwarded headers?
[ ] Apakah service mempercayai forwarded headers?
[ ] Apakah gateway menghapus spoofed inbound headers?
[ ] Apakah audit log butuh real client IP?
[ ] Apakah absolute URL generation dipakai?
16. Session, Cookies, and Stateless API Discipline
Servlet container mendukung session.
JAX-RS enterprise APIs biasanya sebaiknya stateless kecuali ada alasan kuat.
Risiko session di backend API:
sticky session requirement
harder horizontal scaling
state loss on pod restart
cross-tenant leakage risk
harder debugging
unclear auth/session boundary
Cookie masih mungkin dipakai untuk browser-facing API, tetapi service-to-service API biasanya lebih jelas dengan token/header identity.
PR review question:
Is this API intentionally stateful?
If yes, where is session state stored and how is it replicated/expired/audited?
Internal verification checklist:
[ ] Apakah `HttpSession` dipakai?
[ ] Apakah cookie auth dipakai?
[ ] Apakah sticky session diperlukan?
[ ] Apakah session state bisa cross-tenant?
[ ] Apakah session timeout dan invalidation jelas?
17. Graceful Shutdown in Servlet Runtime
Ketika pod/container dihentikan, runtime harus berhenti menerima request baru dan menyelesaikan request in-flight sebisa mungkin.
Ideal lifecycle:
readiness turns false
load balancer stops routing new traffic
container receives termination signal
server stops accepting new connections
in-flight requests drain within grace period
resources close
process exits cleanly
Failure mode:
readiness remains true during shutdown
pod killed before request finishes
DB transaction interrupted
partial response sent
duplicate retry from client
resource cleanup not executed
Internal verification checklist:
[ ] Apakah readiness berubah saat shutdown?
[ ] Apakah SIGTERM ditangani?
[ ] Apakah container/server melakukan graceful stop?
[ ] Berapa termination grace period?
[ ] Apakah long-running endpoint aman saat pod shutdown?
18. Common Runtime Failure Modes
18.1 Resource exists but URL returns 404
Possible causes:
wrong context path
wrong servlet mapping
wrong @ApplicationPath
wrong @Path
proxy rewrite mismatch
resource not registered
package scanning excluded resource
container deployed old artifact
18.2 Resource method never logs
Possible causes:
request blocked by gateway
request rejected by container
Servlet filter aborted chain
auth filter rejected early
wrong content type caused matching failure
18.3 Body is empty in resource method
Possible causes:
Servlet filter consumed input stream
client sent wrong content type
MessageBodyReader mismatch
multipart provider missing
request size exceeded upstream limit
18.4 Thread pool exhausted
Possible causes:
slow DB
slow downstream HTTP
lock wait
file streaming occupying threads
unbounded blocking operation
retry storm
18.5 Works locally but fails behind gateway
Possible causes:
base path rewrite
forwarded header handling
CORS configured at wrong layer
TLS termination mismatch
body/header limit difference
compression behavior difference
19. Debugging Playbook
When a JAX-RS endpoint behaves unexpectedly in Servlet runtime, follow the boundary order.
1. Client request
method, URL, headers, body, content type
2. External infrastructure
DNS, gateway, ingress, load balancer, rewrite, TLS
3. Container boundary
access log, status, latency, request path, remote address
4. Servlet layer
context path, servlet mapping, filter chain, request limits
5. JAX-RS layer
Application/ResourceConfig, provider registration, request matching
6. Resource method
parameter binding, validation, service call
7. Downstream dependencies
DB, Kafka, HTTP client, Redis, cloud service
Avoid jumping directly to application code.
20. PR Review Checklist
For changes touching Servlet/JAX-RS runtime integration, review:
[ ] Does this change alter context path, servlet mapping, or @ApplicationPath?
[ ] Does this introduce a Servlet filter? If yes, should it be Servlet-level or JAX-RS-level?
[ ] Does any filter read or mutate the request body?
[ ] Are headers added/removed at the correct layer?
[ ] Does this change affect CORS, compression, auth, or correlation propagation?
[ ] Does this depend on Tomcat/Jetty-specific behavior?
[ ] Does this remain testable outside the container?
[ ] Are startup and shutdown lifecycle effects understood?
[ ] Are access logs and application logs still sufficient for debugging?
[ ] Are container-level limits documented and tested?
21. Internal Verification Checklist — CSG Quote & Order
Gunakan checklist ini saat masuk codebase atau onboarding runtime:
Runtime identity
[ ] Apakah service memakai Tomcat, Jetty, GlassFish, Grizzly, atau runtime lain?
[ ] Apakah runtime external container atau embedded?
[ ] Apakah artifact WAR, executable JAR, atau image dengan server bawaan?
Bootstrap
[ ] Di mana JAX-RS application diregistrasi?
[ ] Apakah memakai `web.xml`, initializer, `@ApplicationPath`, atau programmatic bootstrap?
[ ] Apakah Jersey `ServletContainer` terlihat?
Routing
[ ] Apa context path final?
[ ] Apa servlet mapping final?
[ ] Apa base path dari gateway/ingress?
[ ] Apakah ada path rewrite?
Filters
[ ] Apa saja Servlet filters?
[ ] Apa saja JAX-RS filters?
[ ] Layer mana yang menangani auth, logging, CORS, compression?
Limits
[ ] Berapa max header size?
[ ] Berapa max body size?
[ ] Berapa connection timeout?
[ ] Berapa worker thread limit?
[ ] Apakah ada request queue/backlog config?
Operations
[ ] Apakah access log aktif?
[ ] Apakah graceful shutdown teruji?
[ ] Apakah readiness/liveness mencerminkan runtime state?
[ ] Apakah runbook menjelaskan path dari gateway ke resource method?
22. Senior Mental Model
Untuk senior engineer, Servlet container bukan detail kecil.
Ia adalah runtime boundary yang menentukan:
how request enters the application
which thread executes application code
which filters run before JAX-RS
how paths are mapped
which limits reject traffic early
how shutdown behaves
how access logs are produced
JAX-RS resource method adalah bagian tengah dari pipeline, bukan seluruh pipeline.
Model yang benar:
External traffic path + Servlet runtime + JAX-RS runtime + application logic + downstream dependencies = actual API behavior.
Jika debugging hanya melihat annotation @Path, diagnosis akan dangkal.
Jika debugging mulai dari traffic path, container boundary, JAX-RS matching, lalu resource method, diagnosis menjadi production-grade.
23. Key Takeaways
Servlet container owns low-level HTTP runtime.
JAX-RS owns resource-oriented programming model.
Context path, servlet mapping, and @Path are different layers.
Servlet filters and JAX-RS filters are different extension points.
Tomcat and Jetty differ internally but share the same runtime reasoning boundary.
Container-level limits can reject request before JAX-RS sees it.
Access logs and application logs answer different debugging questions.
Graceful shutdown must be verified, not assumed.
Part berikutnya membahas GlassFish, Grizzly, dan standalone runtime model agar kamu bisa membandingkan Servlet container deployment dengan application server atau embedded HTTP runtime.
You just completed lesson 17 in start here. 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.
Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.