This page is the canonical Chappe reference: Maven artifacts, exported JPMS modules, the major public API, the CLI YAML schema, CLI flags, and the pre-compression Maven plugin. For RFC conformance see Concepts and chappe-conformance in the repository.

Maven artifacts

Artifact Role

io.vidocq.chappe:chappe-api:0.1.0-SNAPSHOT

Public API: Server, Router, Handler, Filter, Request, Response, Body, StatusCode, MimeTypes, StaticFileHandler, AcceptEncoding, RequestContext, ServerProvider.

io.vidocq.chappe:chappe-http:0.1.0-SNAPSHOT

HTTP/1.1 + HTTP/2 layer, TLS, ByteBufferPool, HPACK.

io.vidocq.chappe:chappe-core:0.1.0-SNAPSHOT

Server engine (virtual threads, protocol detection, lifecycle, ChappeServerProvider).

io.vidocq.chappe:chappe-cli:0.1.0-SNAPSHOT

Standalone chappe serve launcher (fat jar + scripts).

io.vidocq.chappe:chappe-tests:0.1.0-SNAPSHOT

Integration tests (113 tests).

io.vidocq.chappe:chappe-conformance:0.1.0-SNAPSHOT

HTTP conformance (45 tests against RFC 9110/9112/9113).

io.vidocq.chappe:chappe-bench:0.1.0-SNAPSHOT

Comparative benchmarks (Jetty, Helidon, JDK HttpServer).

io.vidocq.chappe:chappe-static-index-maven-plugin:0.1.0-SNAPSHOT

Maven plugin: O(1) index + .gz sidecars at build (option <compress>gzip</compress>).

io.vidocq.chappe:chappe-examples:0.1.0-SNAPSHOT

Usage samples.

JPMS modules

Module Exports / declarations

io.vidocq.chappe.api

exports io.vidocq.chappe.api;
uses io.vidocq.chappe.api.ServerProvider;

io.vidocq.chappe.http

requires io.vidocq.chappe.api;
exports io.vidocq.chappe.http;
exports io.vidocq.chappe.http.h2;

io.vidocq.chappe.core

requires io.vidocq.chappe.api;
requires io.vidocq.chappe.http;
exports io.vidocq.chappe.core;
provides io.vidocq.chappe.api.ServerProvider with io.vidocq.chappe.core.ChappeServerProvider;

io.vidocq.chappe.cli

requires io.vidocq.chappe.api;
requires io.vidocq.chappe.core;
requires java.net.http;

chappe-api does not opens anything. No module exposes its internals through reflection: every dependency goes through the exports listed above.

Major public API (io.vidocq.chappe.api)

Server

public sealed interface Server extends AutoCloseable {
    void start();
    void stop();
    int port();
    static ServerBuilder builder();
}

ServerBuilder:

  • port(int)

  • bindAddress(String)

  • tls(SSLContext)

  • alpnProtocols(String…​)

  • handler(Handler)

  • executor(Executor) (default: virtual threads)

  • build() returns a Server

Router

Router.builder()
    .get(path, handler)
    .post(path, handler)
    .put(path, handler)
    .delete(path, handler)
    .patch(path, handler)
    .head(path, handler)            // also auto for GET
    .options(path, handler)
    .group(prefix, sub -> sub.filter(...).get(...))
    .mount(prefix, handler)
    .filter(filter)
    .notFound(handler)
    .build();

Behavior:

  • automatic 405 Method Not Allowed with the Allow header when the path matches but not the method.

  • trailing slash transparent: /users and /users/ map to the same route.

  • percent-decoding on path params: /users/John%20DoepathParam("id") = "John Doe".

Filter

public interface Filter {
    Handler apply(Handler next);

    static Filter addHeader(String name, String value);
    static Filter addHeaderIf(BooleanSupplier when, String name, String value);
    static Filter addHeaderIfEnv(String envVar, String expected, String name, String value);
    static Filter gzip();
    static Filter gzip(int threshold);
    static Filter accessLog();                          (1)
    static Filter accessLog(Consumer<String> sink);     (2)

    default Filter andThen(Filter next);
}
1 Logs every request to System.out in extended Apache Combined Log Format (see Observability).
2 Variant with a custom sink (useful to redirect into an application logger).

BuildInfo

public final class BuildInfo {
    public static String version();          // e.g. "0.1.0-SNAPSHOT"
    public static String gitCommit();        // e.g. "8d670fb0"
    public static String buildTimestamp();   // e.g. "2026-05-09T19:43:04Z"
    public static String javaVersion();      // e.g. "25.0.3"
    public static String serverHeader();     // e.g. "Chappe/0.1.0-SNAPSHOT+8d670fb0 (2026-05-09T19:43:04Z)"
}

Loaded once at startup from META-INF/chappe-build.properties (generated by Maven resource filtering plus git-commit-id-maven-plugin when building chappe-api). Used automatically by the HTTP layer to inject the Server and X-Chappe-Build headers.

StaticFileHandler

StaticFileHandler.builder()
    .addPath(Path)                       // filesystem (zero-copy sendfile)
    .addClasspath(String resourceRoot)   // classpath (jar, module)
    .cacheInMemory(boolean)              // ETag cache for classpath resources
    .cacheControl(String)
    .preferPrecompressed(boolean)        // .br / .gz sidecars when Accept-Encoding allows
    .spaFallback(String)                 // 200 on 404 (SPA mode)
    .notFoundFile(String)                // 404 on 404 (mutually exclusive)
    .indexFiles(String...)
    .build();

AcceptEncoding

record Entry(String coding, double qvalue) {}

List<Entry>  AcceptEncoding.parse(String header);
boolean      AcceptEncoding.accepts(String header, String coding);

RFC 9110 §12.5.3 algorithm: q-values, wildcard, implicit identity.

MimeTypes

String mime = MimeTypes.detect(path);    // 26+ types: text/html, application/json, image/png, ...

RequestContext

public final class RequestContext {
    public static final ScopedValue<RequestContext> CURRENT;
    public static Request currentRequest();
    public Map<String, Object> attributes();
}

chappe serve CLI configuration

Flags

Flag Effect

--config FILE

load a YAML file

--root DIR

override static.root

--port N

override server.port (default 8080)

--bind ADDR

override server.bind (default 0.0.0.0)

--fallback PATH

override static.fallback (status 404)

--spa-fallback PATH

enable SPA mode (status 200 on 404)

--cache-control STR

override static.cache-control

--gzip / --no-gzip

enable/disable compression

--access-log / --no-access-log

enable/disable Apache CLF access log on stdout

--header KEY=VALUE

add a header (repeatable, merged into headers.always)

-h, --help

help

Flags override YAML values.

Environment variables

Variable Effect

STAGING=true

enables the headers.staging block (typically X-Robots-Tag: noindex, nofollow)

CHAPPE_ACCESS_LOG=true

enables the access log (equivalent to --access-log; CLI flag takes precedence)

YAML schema

server:
  port: 8080
  bind: 0.0.0.0

static:
  root: /var/www/site
  fallback: /404.html               # status 404
  spa-fallback: /index.html         # status 200 (mutually exclusive with fallback)
  index-files: [index.html]
  cache-control: "max-age=3600, public"
  gzip: true                         # negotiated through Accept-Encoding

headers:
  always:                            # injected on every response
    X-Content-Type-Options: "nosniff"
    Referrer-Policy: "strict-origin-when-cross-origin"
  staging:                           # active when env var STAGING=true
    X-Robots-Tag: "noindex, nofollow"

logging:
  level: INFO
  access-log: false

Documented subset: nested maps, inline lists [a, b] and block - item, string/int/bool scalars, # comments. No anchors, no !! tags, no multilines.

Observability

Build identification headers

Every HTTP response from Chappe carries two headers identifying the exact binary that produced it:

Server: Chappe/0.1.0-SNAPSHOT+8d670fb0 (2026-05-09T19:43:04Z)
X-Chappe-Build: Chappe/0.1.0-SNAPSHOT+8d670fb0 (2026-05-09T19:43:04Z)

Format: Chappe/<Maven version>+<short Git hash>(<build timestamp ISO-8601 UTC>). Read once at startup via BuildInfo (see public API).

  • Server is the standard header. It can be rewritten by an upstream reverse proxy (NPM, openresty, Cloudflare).

  • X-Chappe-Build mirrors the same value in a non-standard header that proxies usually forward untouched — useful to identify the binary behind a proxy that sets its own Server.

Idempotency: if the application has already set either header on the Response, Chappe will not overwrite it.

Access log

Enabled via --access-log (CLI), CHAPPE_ACCESS_LOG=true (env), or logging.access-log: true (YAML). CLI overrides env, env overrides YAML.

Format: extended Apache Combined Log Format on System.out (captured by Docker/Portainer):

1.2.3.4 - yann.blazart@gmail.com [09/May/2026:18:50:54 +0000] "GET /a.png HTTP/1.1" 200 877719 12ms

Field resolution:

  • Client IP: X-Forwarded-For (first hop) → X-Real-IPRequest.remoteAddress()-.

  • User: X-Forwarded-UserGap-Auth (oauth2-proxy) → -.

  • Size: Response.body().contentLength() (or - for streamed/chunked bodies).

  • Duration: nanoseconds measured by the outermost filter wrapper, captures the final status even if the handler throws.

Available as a public API filter via Filter.accessLog() / Filter.accessLog(Consumer<String>); the CLI launcher applies it as the outermost wrapper.

Maven plugin: pre-compression

<plugin>
  <groupId>io.vidocq.chappe</groupId>
  <artifactId>chappe-static-index-maven-plugin</artifactId>
  <executions>
    <execution>
      <goals><goal>index</goal></goals>
      <configuration>
        <compress>gzip</compress>            <!-- default: none -->
        <compressThreshold>1024</compressThreshold>
      </configuration>
    </execution>
  </executions>
</plugin>

Generates:

  • META-INF/chappe-static-index.properties — O(1) index (path → size + ETag).

  • .gz sidecars for files above the threshold.

Served zero-copy by StaticFileHandler.preferPrecompressed(true).

HTTP capabilities

Spec Status Notes

RFC 9110 (HTTP Semantics)

OK

Date, HEAD/204/304, 405+Allow, Host, 100-continue

RFC 9112 (HTTP/1.1)

OK

request parsing, chunked, keep-alive, pipelining

RFC 9113 (HTTP/2)

OK

framing, flow control, lifecycle, GOAWAY, SETTINGS, CONTINUATION

RFC 7541 (HPACK)

OK

static table, dynamic table, Huffman encode/decode

RFC 9114 (HTTP/3)

planned

see lien:https://codeberg.org/Vidocq/chappe/blob/main/BUG.md[BUG.md]

RFC 8441 (WebSocket H2)

planned

tracked in BUG.md

Compatibility

  • Java: 25 (LTS), no preview flag required (ScopedValue is final since JEP 506).

  • Maven: 3.9.16 (POM modelVersion 4.0.0).

  • JPMS strict: no classpath, no --add-opens.

Bugs and benchmarks

  • lien:https://codeberg.org/Vidocq/chappe/blob/main/BUG.md[BUG.md] — reproducible bugs, status, minimal repro.

  • lien:https://codeberg.org/Vidocq/chappe/blob/main/BENCH.md[BENCH.md] — performance numbers, hardware, JVM, command.

Source

All code and roadmap live at https://codeberg.org/Vidocq/chappe.