Consolidated technical reference. Use this page as an anchor for external links (issues, RFCs, other Antora sites).
Maven artefacts
| Artefact | Role |
|---|---|
|
Public SPI: |
|
Servlet 6.1 engine — dispatcher, filter chain, sessions, error pages, listeners, security, |
|
Bootstrap on the Chappe transport ( |
|
CDI bridge to Vauban ( |
|
Build-time annotation processor (no runtime role, never shipped in the application). Generates the |
|
Arquillian harness for the official TCK — in-reactor, gated behind the |
Java modules and exports
module io.vidocq.foy.api {
requires transitive jakarta.servlet;
requires static jakarta.annotation;
exports io.vidocq.foy.spi.session; // SessionStore
exports io.vidocq.foy.spi.security; // SecurityProvider, AuthenticatedUser
exports io.vidocq.foy.spi.gen; // WebComponent, WebComponentDescriptor
exports io.vidocq.foy.spi.cdi; // CdiWebComponents
}
module io.vidocq.foy.core {
requires transitive io.vidocq.foy.api;
requires transitive jakarta.servlet;
requires static jakarta.cdi;
requires static jakarta.annotation;
requires java.xml;
requires static java.net.http;
requires io.vidocq.chappe.api; // M1 coupling — will become an SPI in M2
exports io.vidocq.foy.internal.async;
exports io.vidocq.foy.internal.boot;
exports io.vidocq.foy.internal.bridge;
exports io.vidocq.foy.internal.container;
exports io.vidocq.foy.internal.dispatcher;
exports io.vidocq.foy.internal.error;
exports io.vidocq.foy.internal.http;
exports io.vidocq.foy.internal.listener;
exports io.vidocq.foy.internal.security;
exports io.vidocq.foy.internal.session;
exports io.vidocq.foy.internal.webxml;
}
module io.vidocq.foy.chappe {
requires transitive io.vidocq.foy.api;
requires io.vidocq.foy.core;
requires io.vidocq.chappe.api;
requires jakarta.servlet;
requires jakarta.cdi;
exports io.vidocq.foy.chappe; // FoyChappeBoot
}
module io.vidocq.foy.cdi.vauban {
requires transitive io.vidocq.foy.api;
requires jakarta.cdi;
requires io.vidocq.vauban.core;
exports io.vidocq.foy.cdi.vauban; // FoyVaubanBootstrap
}
module io.vidocq.foy.processor { // build time only
requires java.compiler;
exports io.vidocq.foy.processor;
provides javax.annotation.processing.Processor
with io.vidocq.foy.processor.FoyWebComponentProcessor;
}
foy-core additionally declares uses io.vidocq.foy.spi.gen.WebComponent.
|
The |
Build-time code generation
-
foy-processoris a build-time annotation processor with a single requirement,java.compiler. It generatesX$$FoyComponentfor@WebServlet,@WebFilter,@WebListener, for unannotatedServlet/Filter/ listener classes and forServletContainerInitializer(with its@HandlesTypes), carries@MultipartConfig,@ServletSecurity,@DeclareRolesand@RunAsmetadata, and writesMETA-INF/services/io.vidocq.foy.spi.gen.WebComponentplusMETA-INF/foy/class-index.list. Spec misuse is a compile error; a class that cannot be generated is reported as a note. -
FoyWebExtension(infoy-cdi-vauban) is a CDI Build Compatible Extension. It runs insidevauban-processor, which is itself build-time only: unscoped web components become@Dependent, duplicate servlet or filter names fail the build, and a synthetic@Singletonio.vidocq.foy.spi.cdi.CdiWebComponentsbean lists the components forWebAppDiscovery, which merges the beans of the application and of every library archive. A web-annotatedServlet,FilterorEventListenerbean missing from every index (its archive was built withoutfoy-cdi-vaubanon the processor path) is still discovered, with oneWARNINGper class. -
See Component resolution tiers for the runtime side and Usage for the setup.
Public API: FoyChappeBoot
| Method | Description |
|---|---|
|
Returns a fresh |
|
Required. The |
|
URL prefix served by this container. Default: |
|
Session timeout in seconds, rounded up to whole minutes; a |
|
Class loader used for resources and class resolution during deployment. |
|
Optional |
|
Deploys the web application through |
|
The |
|
Mount prefix to use ( |
|
The container’s |
|
Destroys servlets and filters and fires |
Public SPI (foy-api)
| Type | Role |
|---|---|
|
Session storage backend. Default: |
|
Application authentication. Default: |
|
Immutable authenticated-user representation (login, roles, attributes). |
web.xml and web-fragment.xml coverage
WebXmlParser reads the Servlet 6.1 web-app_6_1 schema and the web-fragment variant. The root element is checked, DOCTYPE is disallowed, and a parse error names the offending element. Descriptors are merged with the annotated components per Servlet 6.1 §8.2.3: web.xml wins on a same-name conflict, a conflict between two fragments fails the deployment, a url-pattern claimed by two components fails (§12.2), and metadata-complete="true" ignores annotations and fragments. <init-param> duplicates keep the first value; <async-supported> is tri-state (unset, true, false); <session-config>/<cookie-config> merges per sub-element.
| Element | Status | Notes |
|---|---|---|
|
applied |
|
|
applied |
|
|
skipped |
no JSP engine: logged at |
|
parsed |
not applied |
|
applied |
|
|
applied |
the session cookie follows |
|
applied |
feeds |
|
applied |
|
|
applied |
used when the builder sets no context path; web.xml only — a fragment’s is ignored with a warning |
|
applied |
consumed by the container default servlet: static file first, then servlet; see Welcome files |
|
parsed |
enforced with the security phase |
|
parsed |
enforced with the security phase |
|
applied |
fragment name and §8.2.2 ordering |
|
ignored |
no JSP engine and no naming environment in Foy |
The version and metadata-complete attributes of <web-app> and <web-fragment> are honoured. A DOCTYPE (Servlet 2.2 and 2.3 descriptors) is accepted without being resolved: the public id sets the version, a version before 2.5 implies metadata-complete, and an entity reference is rejected (see Legacy deployment descriptors).
HttpServletMapping
HttpServletRequest.getHttpServletMapping() is implemented for every dispatch type; getMatchValue(), getPattern(), getServletName() and getMappingMatch() follow Servlet 6.1 §12.2:
| Request | MappingMatch |
getPattern() |
getMatchValue() |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
any other path, container default servlet or application |
|
|
|
On a forward the mapping is that of the target; jakarta.servlet.forward.mapping keeps the mapping of the original request, and jakarta.servlet.include.mapping the one of the included target. The mapping of a nested forward stays the original one. A named dispatch sets no mapping attribute.
Dispatcher attributes
| Dispatch | Attributes set on the target request |
|---|---|
|
|
|
|
|
|
|
|
Session configuration
| Setting | Behaviour |
|---|---|
Timeout |
|
|
Time of the previous request. |
Tracking modes |
Default |
|
Keeps the attributes, fires |
Cookie |
Built from |
URL rewriting |
|
Request identity and connection
getRequestId() is unique per request (a JVM-wide counter). getServletConnection() reports the protocol, the isSecure() flag, and a connection id derived from the socket address pair (remote-ip:port-local-ip:port, ?-? when Chappe gives no address). Two limits come from the Chappe Request API, which exposes neither a connection identity nor the HTTP/2 stream id (BUG-20261009-09, open, a Chappe follow-up):
-
the connection id is not unique for the lifetime of the JVM: two connections reusing the same ephemeral port get the same id;
-
getProtocolRequestId()is""for HTTP/2 instead of the stream id, andgetProtocolConnectionId()is""on every protocol.
ServletContext.getVirtualServerName() returns vidocq unless FoyChappeBoot.Builder.virtualServerName(…) sets it. getRealPath(path) answers the file-system path of a resource held in a directory (canonicalised first), and null for a resource inside a jar or a missing one.
Servlet 6.1 API comparison
| Area | Foy status | Notes |
|---|---|---|
Core ( |
✅ |
Near-complete coverage in the official TCK. |
|
✅ |
Parameter merge, named dispatch of unmapped servlets, nested forward keeping the originals, response closed after a forward. Cross-context ( |
Default servlet, welcome files, |
✅ |
Static content with conditional GET and ranges, welcome files, |
Web fragments ( |
✅ |
Implemented — discovery, §8.2.2 ordering and §8.2.3 merge; see |
Async ( |
✅ |
Cycles, |
Response streaming and buffering |
✅ |
Commit on buffer overflow, |
Non-blocking I/O ( |
✅ |
On async-started requests and upgraded connections. Not resumed after an |
Response trailers ( |
✅ |
HTTP/1.1 chunked and HTTP/2; supplier called after the body on Chappe’s thread; see Usage for the limits. |
HTTP Upgrade ( |
✅ |
HTTP/1.1 only; blocking and non-blocking streams; |
HTTP/2 server push ( |
❌ |
Not supported: |
Multipart ( |
⚠️ |
Buffered in memory — no streaming parse, no temp-file spill; limit/threshold handling partial (8 of 14 |
Sessions |
✅ |
|
Security (Basic) |
⚠️ |
|
Security (Form, Digest, Jakarta Auth) |
❌ |
Not implemented. |
WebSocket (Jakarta WebSocket over the upgrade) |
❌ |
Not implemented (planned, no date); HTTP Upgrade itself is available. |
JSP |
❌ |
Not supported by Foy itself; planned through a JSP-engine SPI plugged by Ibarra (Jakarta Pages 4.0). |
Other CDI containers NEW
Foy does not need Vauban: give FoyChappeBoot the BeanManager of any CDI 4 container (CDI.current().getBeanManager()). Without foy-cdi-vauban’s build-time index, Foy finds the web components by walking the container’s `Servlet, Filter and EventListener beans (foy#18).
-
The request context is active for each request — Foy activates it through the standard
RequestContextControllerbefore the application’s request listeners, and deactivates it after them, on the thread that served the request (an asynchronous request included: Foy waits for it).@RequestScopedbeans work in servlets, filters and listeners, and the container fires@Initialized(RequestScoped.class)and@Destroyed(RequestScoped.class). If the context is already active (an embedding runtime activated it), Foy leaves it alone. Up to 0.3.0, Foy activated it under no container, Vauban included (BUG-20261010-01). On Vauban, this needs a Vauban with its built-inRequestContextController(vauban#147); before it, the request context was not activated under Vauban and a@RequestScopedbean failed withContextNotActiveException. -
NEW The session context, on Vauban —
@SessionScopedbeans live in theHttpSession: one instance per session, created on first use (which creates the session), destroyed with it when it is invalidated or expires, between@BeforeDestroyedand@Destroyed(SessionScoped.class); a new session fires@Initialized(SessionScoped.class). CDI Lite leaves the session context to the servlet container (CDI 4.1 §6.7.2): Vauban’svauban-webcontextsprovides it, andfoy-cdi-vauban, which depends on it, drives it from Foy’s request and session events (foy#21). Nothing to configure. Passivation is not covered: the instances are not written out with the session. -
The session context, on another container — Foy finds the listeners that drive a CDI implementation’s contexts through the
io.vidocq.foy.spi.cdi.CdiContextListenersservice, registers them ahead of the application’s listeners when it is given that implementation’sBeanManager, and an adapter such asfoy-cdi-vaubanprovides them. Unlike aServletContainerInitializer, such a provider does not make Foy start an application that has no web component. Weld’s own servlet integration,weld-servlet-core, also runs on Foy: itsServletContainerInitializerstarts Weld and manages the request and session contexts itself; Foy is then given noBeanManager, and servlets declared inweb.xmlreach their beans throughCDI.current().
foy-it-weld, part of every build and never published, runs Foy on Chappe with Weld SE 6.0 (CDI 4.1) on a class path, without foy-cdi-vauban: a servlet, a filter and a listener get an injected bean, a @RequestScoped bean is one instance per request, and the request context events fire. No Open Liberty module: on a server, use the server’s own Servlet container.
NEW The session scope has its own pair of modules, never published, running the same HTTP scenarios from foy-it-session-scenarios: foy-it-weld-servlet, the reference, where Weld’s servlet integration manages the session context, and foy-it-vauban-session, where vauban-webcontexts does. Both check one cart per session across requests, two carts for two clients, a new and empty cart after invalidate() with the old one’s @PreDestroy run, and the session events.
Compatibility
-
Java 25 (LTS)
-
Maven 3.9.16
-
Jakarta Servlet 6.1
-
Any CDI 4 container (Vauban, Weld) — see Other CDI containers NEW
-
Strict Java Modules, virtual threads (a Foy VT per request, next to Chappe’s connection VT)
-
AOT-friendly (no reflection, no dynamic proxy)