Catfish is a Java library for embedding an HTTP/1.1 server into any JVM application. It provides low-level control over the HTTP protocol without imposing a framework: no annotation scanning, no dependency injection, no servlet container (though an optional servlet bridge is available).
Key capabilities: non-blocking I/O, TLS with SNI-based virtual hosting, streaming responses, and keep-alive / compression policies per virtual host.
- Java 21+
- Bazel with bzlmod for building
HttpHandler is a single-method interface:
// Buffered response — small, complete responses assembled in memory
HttpHandler handler = (connection, request, writer) ->
writer.commitBuffered(StandardResponses.OK);For large or dynamic output use commitStreamed, which sends the body with chunked
transfer encoding:
HttpHandler handler = (connection, request, writer) -> {
HttpResponse response = StandardResponses.OK.withHeaderOverrides(
HttpHeaders.of(HttpHeaderName.CONTENT_TYPE, MimeType.TEXT_HTML.toString()));
try (Writer out = new OutputStreamWriter(
writer.commitStreamed(response), StandardCharsets.UTF_8)) {
out.append("<!DOCTYPE html><html>...");
}
};CatfishHttpServer server = new CatfishHttpServer(eventListener);
server.addHttpHost(
"localhost",
UploadPolicy.DENY,
ResponsePolicy.KEEP_ALIVE,
handler,
/* sslContext= */ null);
server.listenHttp(8080);
server.listenHttps(8443); // requires an SSLContext — see TLS section belowCall server.stop() to shut down.
SSLContextFactory provides two helpers for loading credentials:
PKCS#12 bundle:
try (InputStream in = new FileInputStream("server.p12")) {
SSLContext sslContext = SSLContextFactory.loadPkcs12(in);
}PEM key + certificate files (also extracts the CN as the virtual-host name):
SSLContextFactory.SSLInfo sslInfo =
SSLContextFactory.loadPemKeyAndCrtFiles(keyFile, certFile);
server.addHttpHost(
sslInfo.getCertificateCommonName(),
handler,
sslInfo.getSSLContext());
server.listenHttps(8443);SNI is used to select the right SSLContext for each incoming connection. Connections that
present an unknown hostname receive a TLS unrecognized_name alert before the handshake
completes.
- Non-blocking NIO — a selector-thread pool handles all socket I/O without blocking; application handlers run on a separate worker pool.
- Pipeline stages — TLS and HTTP are independent, composable layers. For HTTPS the stack is: TLS decryption → HTTP parsing → handler → HTTP response → TLS encryption.
- SNI-aware TLS — the server inspects the ClientHello to pick the right certificate (and reject unknown hostnames with a TLS alert) before completing the handshake.
- Dual response modes —
commitBufferedfor small responses (addsContent-Length, optional gzip);commitStreamedfor large or dynamic output (chunked encoding, backpressure). - Virtual hosting — each hostname has its own handler, TLS context, keep-alive policy, and upload policy.
Collect coverage data first:
bazel coverage //javatest/...
Text summary (per-file percentages, sorted ascending):
bazel run :coverage_report
HTML report written to .coverage/index.html (requires lcov):
bazel run :coverage_html