Spider Silk
Thin call stack, strong signature.
Spider Silk is a web framework built on the Jakarta Servlet API.
Values
Fast startup
An application’s startup time is kept to a minimum. Startup is a cost paid again at every main point of development and operation: autoscaled containers, serverless environments, test runs, and development-server restarts.
Code traceable from its source
The code a request passes through on its way to a response can be followed by reading the source alone. Human developers and AI coding agents both need that traceability. For a person, code that is easy to trace is code that is easy to understand and debug. An AI agent reasons about behavior from the code before running it, so it fills behavior the code does not show with a guess. An agent that guesses wrong about what an annotation does at runtime, for example, writes code that compiles and does not work.
Design and technology choices
This section describes the design and the technology the framework chose to realize the three values above.
A simple, intuitive API
Spider Silk keeps its core API intuitive and simple, for traceability.
A handler, the core interface, receives the HTTP request as a method parameter of type WebRequest and returns the response as a WebResponse.
@FunctionalInterface
public interface Handler {
WebResponse handle(WebRequest request) throws Exception;
}
A handler is mapped to a URL and an HTTP method as a lambda expression or a method reference.
var deckRepository = new DeckRepository(dataSource);
var deckService = new DeckService(deckRepository);
var app = new App();
app.get("/decks/{deckId}", req -> {
long deckId = req.pathParamLong("deckId");
Deck deck = deckService.getDeck(deckId);
return WebResponse.template("deck", Model.of("deck", deck));
});
app.get("/api/decks", req -> {
JsonArray resBody = Json.array();
for (DeckSummary deck : deckService.deckSummaries()) {
resBody.add(Json.object()
.put("id", deck.id())
.put("name", deck.name()));
}
return WebResponse.json(resBody);
});
app.post("/api/decks", req -> {
String name = req.bodyJson(json -> json.asObject().getString("name"));
Deck deck = deckService.createDeck(name);
JsonObject resBody = Json.object()
.put("id", deck.id())
.put("name", deck.name());
return WebResponse.json(resBody).status(HttpStatus.CREATED);
});
app.start(8080);
No reflection
The Spider Silk framework’s own code uses no reflection. There is no annotation scanning and no proxy generation at application startup, so startup is faster. The framework’s call stack is easy to follow, so the code is more traceable as well. An application also compiles to a GraalVM native image more easily.
A minimal call stack
On its main call path, the handler, the framework adds only two stack frames above HttpServlet.service.
The other places the framework calls application code, such as filters, exception handlers, and streamed bodies, stay within four frames.
The fewer frames a stack trace holds, the easier the code at fault is to reach, so the code is more traceable.
This is a stack trace taken inside a handler.
java.lang.Throwable: where a handler stands
at com.example.decks.DeckRoutes.one(DeckRoutes.java:15) // the handler
at net.benelog.spidersilk.AppServlet.dispatch(AppServlet.java:299) // Spider Silk
at net.benelog.spidersilk.AppServlet.service(AppServlet.java:149) // Spider Silk
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java) // the Servlet API
at org.eclipse.jetty.ee10.servlet.ServletHolder.handle(ServletHolder.java) // Jetty, from here down
A default stack with a minimal footprint
Spider Silk chose its default stack for fast startup and traceable code.
spider-silk-core includes Jetty as its web server and jte as its template engine by default.
With one dependency, an application starts with a server and a template engine in place.
Jetty
The default web server is embedded Jetty. Of the three servers, Jetty provides server lifecycle management as its own features most completely. With Jetty, therefore, less of this framework’s code stands between the application and the server than with Tomcat or Undertow. Jetty provides these features on its own:
-
Jetty’s own
stopTimeouthandles a graceful stop,setStopAtShutdownthe shutdown hook, and Jetty’s non-daemon threads keep the JVM alive. -
It runs without a working directory on disk.
-
Sessions can be turned off with
sessions(false).
jte
The default template engine is jte.
jte compiles templates to Java source.
An expression such as ${deck.name()} therefore runs as a compiled method call.
jte does not read model values by reflection, so it fits the no-reflection principle.
A precompiled template needs no compiler at runtime, so it works well in a GraalVM native image too.
In development, a DirectoryCodeResolver reading the source directory shows an edited template without a rebuild (Development and production).
An extensible module structure
Spider Silk provides optional modules so that widely used technologies beyond the default stack can be used as well.
Every module depends on spider-silk-core, and core depends on none of them but silk-json, the JSON library it is built on.
| Category | Module | Contents | Dependencies |
|---|---|---|---|
Default |
|
The framework itself. The default web server is Jetty, and the default template engine is jte. |
|
JSON |
The JSON library core is built on: writers, readers, the tree, and |
the JDK only at run time |
|
Binds with Jackson through the |
core, |
||
Web server |
Runs the same application on embedded Tomcat. It suits an organization that already operates Tomcat, or an application moving over from Spring Boot. |
core, |
|
Runs the same application on embedded Undertow. Undertow’s graceful stop counts requests, so draining works on a virtual-thread executor too. |
core, |
||
Template engine |
Renders with FreeMarker. |
core, |
|
Renders with Handlebars.java. |
core, |
||
Renders with Thymeleaf. |
core, |
||
WebSocket |
Maps WebSocket endpoints beside the application’s HTTP routes.
It is Jetty-only by construction, so it is a module named after Jetty rather than a method of |
core, |
|
Testing |
The |
core, and otherwise the JDK only (the servlet API compile-time only, as in core) |
|
Example |
|
A flashcard study application |
core, spring-jdbc, H2 |
Routing, filters, status pages, JSON, templates, static files, SSE, multipart uploads, and sessions behave the same on all three servers.
The APIs of TomcatServer and UndertowServer follow JettyServer’s, so switching servers changes one line: the factory passed to `App.server(…).
The template engine is switched with App.templates(…).
Switching engines leaves a handler’s WebResponse.template(name, model) call as it is.
The dependencies between the modules are shown in the figure below.
Development tool integration
Spider Silk provides tools for testing, API documentation, observability, builds, and AI coding agents as separate modules and a skill, so that it fits the development tool ecosystem.
-
The
WebTestharness ofspider-silk-teststarts the application on a free port and hands over a client that keeps cookies.
-
spider-silk-openapiturns the route listapp.routes()answers into an OpenAPI 3.1 document. Routes are plain data, so the module works without reflection.
-
spider-silk-opentelemetry-agentis an extension of the OpenTelemetry Java agent that names each server span after the route that answered it, such asGET /api/decks/{deckId}. Without it the agent sees one servlet mapped at/*and names every spanGET /*.
-
The Gradle plugin
net.benelog.spidersilkcarries the conventions for precompiled jte templates, Jib container images, and GraalVM native compilation. -
spider-silk-maven-parentprovides the same packaging conventions as a Maven parent POM.
-
The agent skill lives in
skills/spider-silk/in the Agent Skills format, and guides an agent to use the framework as intended rather than by guessing. Agent Skills is an open standard and the skill uses no feature particular to one tool, so Claude Code, Codex, Cursor, GitHub Copilot, and other AI agents read the same directory as it is. The repository doubles as a plugin marketplace, so Claude Code installs it with two commands:
/plugin marketplace add benelog/spider-silk
/plugin install spider-silk@spider-silk
The repository is a Codex plugin marketplace as well, so Codex installs it with two commands too:
codex plugin marketplace add benelog/spider-silk
codex plugin add spider-silk@spider-silk
Performance
In the benchmark in the appendix, Spider Silk on Jetty answers plain text about 1.42 times as fast as Spring Boot and a JSON list of 100 records about 1.7 times, and its JVM process starts in a third of the time. The benchmark ran both frameworks on one laptop with the same JVM and the same load, against Spring Boot 4.1 with Spring Web MVC, Jackson, and Thymeleaf on its defaults.
| Measure | Spider Silk (Jetty) | Spring Boot |
|---|---|---|
Plain text, requests/s |
256,545 |
180,984 |
Small JSON object, requests/s |
256,318 |
180,642 |
JSON list of 100 records, requests/s |
163,817 |
95,200 |
JSON list of 100 records as a request body, requests/s |
114,480 |
55,860 |
HTML page, requests/s (jte vs. Thymeleaf) |
197,418 |
63,927 |
Startup on the JVM |
295 ms |
902 ms |
Startup as a GraalVM native image |
24 ms |
37 ms |
At the JSON list, where the serializer does most of the work, Spider Silk answers about 1.7 times as many requests as Spring Boot with Jackson, and about twice as many when it reads the list from a request body. With Thymeleaf on both sides and the same container, the two render a page within 2% of each other, so the margin on the HTML page is jte’s. As a GraalVM native image, Spider Silk keeps more of its JVM throughput than Spring Boot does, both as a GraalVM CE image and as an Oracle GraalVM image built with profile-guided optimization (PGO). Each cell below is a native image’s throughput as a share of the same application’s on the JVM.
| Case | Spider Silk, GraalVM CE | Spring Boot, GraalVM CE | Spider Silk, PGO | Spring Boot, PGO |
|---|---|---|---|---|
Plain text |
75% |
65% |
93% |
84% |
Small JSON object |
80% |
69% |
94% |
86% |
JSON list of 100 records |
60% |
14% |
92% |
27% |
JSON list of 100 records as a request body |
73% |
14% |
90% |
27% |
HTML page (jte vs. Thymeleaf) |
71% |
57% |
92% |
89% |
With Thymeleaf on both sides, Spider Silk’s images keep less: 40% on GraalVM CE and 79% with PGO, against Spring Boot’s 57% and 89%. The appendix covers the method, the machine, the native images, and the limits of these numbers.