Premier servlet, container Foy, transport Chappe : moins de cinquante lignes de Java, pas de web.xml obligatoire, pas de scan classpath runtime.

Pré-requis

  • Java 25 (Temurin) et Maven 3.9.16.sdkmanrc est versionné à la racine du repo, lancer sdk env avant tout.

  • Familiarité avec l’API Jakarta Servlet 6.1.

  • Pour la version CDI : un BeanManager Vauban (voir Vauban — démarrage rapide).

Coordonnées Maven

<dependencies>
    <dependency>
        <groupId>jakarta.servlet</groupId>
        <artifactId>jakarta.servlet-api</artifactId>
        <version>6.1.0</version>
    </dependency>
    <dependency>
        <groupId>io.vidocq.foy</groupId>
        <artifactId>foy-core</artifactId>
        <version>0.1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>io.vidocq.foy</groupId>
        <artifactId>foy-chappe</artifactId>
        <version>0.1.0-SNAPSHOT</version>
        <scope>runtime</scope>
    </dependency>
    <!-- CDI optionnel via Vauban -->
    <dependency>
        <groupId>io.vidocq.foy</groupId>
        <artifactId>foy-cdi-vauban</artifactId>
        <version>0.1.0-SNAPSHOT</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

Hello world : une Servlet annotée

package io.example;

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/hello/*")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        var name = req.getPathInfo() == null ? "world" : req.getPathInfo().substring(1);
        resp.setContentType("text/plain;charset=UTF-8");
        resp.getWriter().write("Hello, " + name + "!");
    }
}

L’annotation @WebServlet est résolue à la compilation par un APT. Aucun scan classpath au démarrage.

Démarrer le container

L’API publique côté Chappe est FoyChappeBoot. Elle prend un BeanManager (CDI) et expose un Handler Chappe à monter sur un ChappeMountPoint.

import io.vidocq.foy.chappe.FoyChappeBoot;
import jakarta.enterprise.inject.spi.CDI;

void main() throws Exception {
    var mounted = FoyChappeBoot.builder()
            .beanManager(CDI.current().getBeanManager())
            .contextPath("/app")
            .sessionTimeoutSeconds(1800)
            .build()
            .orElseThrow(() -> new IllegalStateException("Aucun @WebServlet découvert"));

    chappeMountPoint.mount(listener, mounted.mountPrefix(), mounted.handler());
    mounted.fireContextInitialized();
}

build() retourne un Optional<Mounted> vide quand l’application ne déclare aucun bean Servlet/Filter/Listener — l’appelant choisit s’il s’agit d’une erreur ou d’un cas dégradé acceptable.

Annotations ou web.xml ?

Foy supporte les deux et applique la sémantique metadata-complete standard.

  • @WebServlet, @WebFilter, @WebListener, @MultipartConfig : découverts à la compilation par APT.

  • WEB-INF/web.xml et META-INF/web-fragment.xml : lus au démarrage par WebXmlParser et fusionnés via WebAppDiscovery.

  • Si metadata-complete=true, les annotations sont ignorées (comportement Servlet 6.1 standard).

Intégration avec Chappe

Le pont est ChappeServletBridge, un Handler Chappe qui :

  1. expose la Request Chappe en tant que HttpServletRequestImpl ;

  2. expose la Response Chappe en tant que HttpServletResponseImpl ;

  3. invoque la chaîne FilterServlet ;

  4. retourne la réponse via ServletOutputStreamImpl adossé au streaming Chappe — pas de copie intermédiaire.

sdk env
./mvnw -ntp install -DskipTests
./mvnw test

TCK Jakarta Servlet 6.1 officiel : ./run-official-tck-servlet6.1.sh --all à la racine du repo foy. Le module foy-tck est volontairement hors-reactor — voir la page TCK.

Et après ?

  • Cas d’usage — filtres, listeners, async, multipart, sessions, dispatchers, virtual hosts, intégration CDI.

  • Concepts — le vocabulaire Servlet, ce qui change avec la 6.1.

  • Fonctionnement interne — séquence d’une requête, modèle de threads, lifecycle container.