sun, 22:25 at the minemoon, waxing crescent, 36% lit - 22:25 at the mine(spicemine)(-_-)(=^・^=)(・_・)

サインインEN日本

mélangej · ABCL on Jooby and Netty

What mélangej is

**Lisp as the language of business logic and templating, the JVM for the
plumbing.** Thirteen files, no Lisp dependencies, and you can read all of
it.

mélangej is the web framework underneath spicemine: Common Lisp running on the JVM, serving HTTP, with no Lisp dependencies. ABCL on Jooby and Netty.

The name is mélange — the spice — plus j for the JVM it runs on. The Lisp package prefix is spice/.

"No dependencies" is a claim people make loosely, so here is the exact version.

What it depends on

No Lisp dependencies. No Quicklisp, no ASDF system of our own, nothing vendored. The framework is thirteen files of Lisp, 7,204 lines, which you can read in a weekend; with spicemine on top, one boot.lisp loads thirty-three files in order, 33,472 lines including the loader. The appendix lists every one.

Java dependencies: twenty-nine jars, 40 MB.

  • ABCL 1.9.2 — Armed Bear Common Lisp, an ANSI Common Lisp that runs on the JVM. This is the whole Lisp. Chapter 2 is about exactly which Lisp that is.
  • Jooby 3.9.1 and jooby-netty — routing and the HTTP server.
  • Netty 4.2 — the socket layer Jooby sits on.

Then, per feature rather than per framework: JGit for the git engine, sqlite-jdbc and the PostgreSQL driver for metadata, minimal-json where Java needs to read JSON. An instance that did not host git would not need JGit.

Why that distinction is the whole design

Netty is used by a very large number of applications and is tested on every JVM anyone runs. When something in it breaks, somebody else found it first.

Common Lisp libraries are mostly written and tested on SBCL. Many work fine on ABCL. Some do not, and you find out when you try to load one. Each library you add brings everything it depends on, so a single quickload can be thirty bets at once — and the thirty are not all maintained by people who have ever run ABCL.

mélangej makes that bet zero times. The cost is that some things you would normally install, you write: this project has its own Markdown renderer, HTML emitter, template compiler, JSON reader and writer, and schema migration. Each is a few hundred lines. That is affordable because each is small, and it is not affordable for everything — chapter 12 says where the line is.

What this book does not argue

The security of the platform. Not because it does not matter, but because it is not ours to argue. The JVM, Netty and the JDBC drivers are read by more security engineers in a week than this project will meet in its life, and a fix for a hole in any of them arrives as a version bump. That is what the stack buys, and it is bought by using the boring thing.

What is ours is above that line: the 33,472 lines of Lisp in this tree, which escape HTML by default, hash passwords with PBKDF2, compare secrets in constant time, and decide who may see what. That is the surface a reader has to trust, and it is small enough to read. The alternative — a Lisp dependency graph of a few hundred packages, each with its own release cadence and its own idea of input validation — would not be safer for being more Lisp. It would be more lines nobody has read.

The files

Every one of these is a link to the file itself, at the tip of trunk:

  • boot.lisp — decides what to load, nothing else
  • spice-http — Jooby wrapped so no other file writes Java interop
  • spice-json — a reader and a writer
  • spice-db — JDBC, a pool, transactions, sqlite or postgres
  • spice-migrate — numbered forward-only migrations with a ledger
  • spice-auth — PBKDF2, tokens, constant-time comparison
  • spice-domain — rows: users, orgs, repos, sessions, invites, keys
  • spice-scm — the version-control adapter protocol (CLOS generics)
  • spice-openapi — the API description, generated from the route table
  • spice-md — a Markdown renderer that escapes before it parses
  • spice-html — HTML that escapes by default
  • spice-tmpl — .html templates with Lisp inside them
  • spice-ds — the Datastar wire format, for page updates without a reload
  • spice-banner — the animated header strip
  • spice-i18n — every string the UI can show, keyed, in two languages
  • spice-skin — ten skins as token blocks
  • spice-web — browser session rules, no HTML
  • spice-ui — the pages
  • api-v0 — the REST surface

Every name above opens that file as it was in the release this page is served from, in the source on this instance; the appendix lists every file of the framework. It runs a browser check against crawlers, so those links are for clicking rather than for curl; chapter 3 has the two ways to fetch a release from a script.

There is no system definition and no dependency graph to declare: the thirty-three-line order in boot.lisp is the graph.

Why Lisp on the JVM

The two halves of the problem have different best answers.

Hosting repositories means talking to JGit, Mercurial's wire protocol, a JDBC driver, a TLS terminator. That is twenty years of library work that exists on the JVM, and is the part you want written by people who have been maintaining it for a decade.

Writing the rules — who may see what, what a page says, what a word means — changes every week. That wants a language where a page is a function, a message catalog is a list, and a theme is an alist you can print at the REPL. That is Lisp.

ABCL joins them. It is a real Common Lisp — conditions, CLOS, macros, the reader — and (java:jcall "method" object arg) calls Java directly, with nothing to load first. What that costs is measured in chapter 11, including where it is expensive.

Why Common Lisp, in 2026

The honest form of the question is: why Lisp now, when a language model writes the first draft of most code anyway?

Because that is exactly when it matters what the code is made of. A first draft nobody can read is a liability with a commit hash. Common Lisp has the properties that make a draft reviewable. The program is data, so a macro that generates routes can be expanded and read. The REPL evaluates the function you are suspicious of, in the running image, against the real rows, and tells you what it returned. The standard has not moved since 1994, so what the model learned about the language is still true, and what you learned about it ten years ago is too. Thirteen files and 7,204 lines is a weekend's reading for a person, and a person reading is the unit that counts. When something else is doing the typing, write in the language that lets one person hold the whole system in their head.

Why the JVM, and not the newer thing

Every year has a runtime that is faster, smaller and newer than the JVM, and chapter 11 measures two of them being exactly that. None of them is the question. The plumbing of a service like this — sockets, TLS, JDBC, git's pack protocol — is maintained by other people, and the only thing that matters about it is who those people are and how long they have been at it. On the JVM that is twenty-five years of production findings folded back into the runtime, a profiler anyone can read, a heap dump format every operations team has seen, and a security record examined by more eyes than any newer thing has users. The runtime that is exciting in 2026 will be a fine choice in 2036 if it is still there; the JVM is already there. mélangej spends its novelty on the Lisp, where it buys something, and none of it on the plumbing, where it would only cost.

Which version this is

mélangej 0.3.0. The framework has its own version number, counted separately from spicemine's, and /api/v0/ping reports both:

{ "service": "spicemine", "version": "0.94.0",
  "platform": "melangej", "platform_version": "0.3.0" }

Two numbers because they are two things with different audiences: the service version tells an operator what is deployed, and the framework version tells a reader of this book which routing, templating and fragment behaviour it describes. They live in one repository today and will not always; MELANGEJ.md lists which files belong to which side, and that list is the split boundary.

The counter starts at 0.1.0 and is not back-dated. Assigning a version to each week before the framework had a name would be a changelog of guesses. Pre-1.0 means the interface is allowed to move, and MELANGEJ.md records what moved.

What this book is

Thirteen more chapters. Chapter 2 says what you are actually standing on: which Common Lisp this is, exactly, and where it stops being portable. Then a quickstart you can run, routing, templating, fragments and Datastar, localization, skins, development tools, deployment, performance with six servers measured side by side, who it is for, and what comes next.

Chapter 14 is the worked example, and it is last on purpose: spicemine is the system this framework was extracted from, and it reads better once you know what the pieces are called. If you would rather see the whole thing before any of the parts, start there and come back.

It describes a running system — spicemine, which serves this page — not a design.

Which Lisp this is →