Before diving into internals, let’s pin down the vocabulary. The terms below come from the Jakarta Servlet 6.1 spec as implemented by Foy.
Core vocabulary
| Term | Meaning in Foy |
|---|---|
Request / Response |
|
Servlet |
A |
Filter chain |
Pipeline of |
Listener |
A bean reacting to container lifecycle ( |
|
Implemented by |
|
|
Default servlet |
The container servlet |
Async |
|
Multipart |
|
Metaphor: the layer above the transport
Chappe moves bytes: it speaks HTTP/1.1 or HTTP/2 over TLS, manages a connection, reads a body. It does not know what a "Servlet" or a "filter" is.
Foy interprets those bytes: it turns an HTTP connection into an HttpServletRequest, finds the Servlet matching the URL, walks the filter chain, writes the response. It is exactly the division the 19th-century French telegraph administration drew between stationnaires (relaying the Chappe code without understanding it) and bureau directors (interpreting the message).
Application discovery
foy-core orchestrates the merge between annotations and the descriptor:
-
reading of annotations (
@WebServlet,@WebFilter,@WebListener,@MultipartConfig) — resolved at compile time by an APT, exposed through the VaubanBeanManager;WebAppDiscoveryperforms that aggregation atFoyChappeBootstartup; -
reading of the descriptor (
WEB-INF/web.xml, supported subset — see Reference) viaWebXmlParser, producing aWebAppDescriptor;DescriptorMergermerges it with the annotation-discovered components following Servlet 6.1 §8.2.3 (the descriptor wins on a same-name conflict;metadata-complete="true"ignores annotations).
Web fragments (META-INF/web-fragment.xml) and `ServletContainerInitializer`s of every jar on the class path or module path are discovered and ordered per §8.2.2 before the merge; see the native layout and the pipeline.
Annotated components are never found by a class scan: they are already known to Vauban at compile time. Only the descriptors and initializers are discovered at start-up.
Chappe ↔ Servlet bridge
foy-core hosts ChappeServletBridge (package io.vidocq.foy.internal.bridge; foy-chappe’s `FoyChappeBoot builds it), a Chappe Handler that:
-
exposes the Chappe
RequestasHttpServletRequestImpl; -
runs the
Filter→Servletchain on a Foy virtual thread (foy-request-<n>), while Chappe’s connection thread waits for the response head; -
hands Chappe the response as soon as it is committed: the head, then a live body fed by the servlet’s output through a bounded pipe (
ResponsePipe), so the client receives the body while the servlet writes it; -
for a response that never committed (it fitted in the buffer), hands Chappe the whole buffered body at the end of the request.
The four Servlet I/O models map onto that bridge:
-
Blocking streams — the default; a write blocks only when the client reads slower than the servlet writes.
-
Async (
AsyncContext) — the request outlivesservice(); the response is completed bycomplete(), a dispatch, a timeout or an error. -
Non-blocking I/O (
ReadListener,WriteListener) — callbacks on Foy virtual threads, never on Chappe’s connection thread, serialised per stream. -
HTTP Upgrade (
HttpUpgradeHandler) — after the101head, Chappe hands the raw connection to Foy, which exposes it as aWebConnection.
Application security
-
BasicAuthenticator— standard HTTP Basic (RFC 7617). -
SecurityConstraintEnforcer— applies@ServletSecurityconstraints (@HttpConstraint,@HttpMethodConstraint) taken from the component descriptor (generated at build time, or decoded from the class bytes).<security-constraint>inweb.xmlis not parsed. -
AnonymousSecurityProvider— open fallback for development. -
Public SPI:
io.vidocq.foy.spi.security.SecurityProviderfor plugging custom providers (LDAP, JWT, OIDC).
Form-based and Digest authentication are not implemented (the spec.security.* TCK family passes 22/59 at the Phase 5 exit). Jakarta Authentication / Authorization: not implemented, later milestone.
CDI: a single BeanManager
foy-cdi-vauban connects Foy to Vauban through a single class: FoyVaubanBootstrap, which returns the current VaubanContainer’s `BeanManager for FoyChappeBoot.
-
Servlet/Filter/Listenerinstances are resolved through theBeanManager, so they can carry@Injectfor application beans. -
ServletContext,HttpServletRequestandHttpSessionare not exposed as CDI beans yet — no@ApplicationScoped/@RequestScoped/@SessionScopedproducers exist for them. -
NEW The CDI request context is active for each request, under any container, Vauban and Weld included, so your own
@RequestScopedbeans work; the session context is active on Vauban, throughvauban-webcontexts(Other CDI containers).
Resolution is fully build-time through Vauban — no dynamic proxy, no hot-path reflection, AOT-friendly (GraalVM, Leyden CDS).
Servlet 6.1 vs 5.0 differences
| Aspect | Servlet 5.0 (Jakarta EE 9.1) | Servlet 6.1 (Jakarta EE 11) |
|---|---|---|
Package |
|
|
Java modules |
Optional |
Recommended — Foy enforces it |
I/O threading |
Platform pool + NIO |
Virtual threads (Foy: one VT per request, next to Chappe’s connection VT) |
HTTP/2 server push |
|
Deprecated — Foy’s |
Legacy API |
|
Removed / clarified |
WebSocket |
Separate spec 2.1 |
Separate spec 2.2 — not implemented in Foy (planned, no date) |
HTTP/3 |
Out of scope |
Out of scope (transport — see Chappe) |
Foy focuses on the relevant 2026 subset: no JSP, no runtime SCI through META-INF/services, no reflection. Libraries that depended on those features need to be ported through an explicit SPI or an APT.