showcase
Showcase
Every sample below is a real file in this website's repository. Each one is linted and run, and the output shown is the output it produced.
-
1 / 10
Hello, and a library call
Declarations are bare, uses carry a sigil, and every library is asked for by name.
01-hello.j 19 lines use io; use strings; # Nothing auto-loads: each library is asked for by name. # Declarations are bare; every use of a variable carries $. def const GREETING as string init "hello"; def name as string init "world"; func shout(word as string) { return strings.upper($word) + "!"; } io.printf("%s, %s\n", GREETING, $name); # Cooked strings interpolate; raw '...' strings never do. def loud as string init shout($name); for (def i in 1..4) { io.printf("{$i}: {$loud}\n"); }jennifer run 01-hello.j
hello, world 1: WORLD! 2: WORLD! 3: WORLD!
-
2 / 10
Structs, lists, JSON
Typed structs, a higher-order filter over a func value, and JSON out - no annotations, no reflection.
02-data.j 33 lines use io; use json; use lists; use strings; def struct Deck { name as string, downloads as int, tags as list of string }; func popular(d as Deck) { return $d.downloads >= 1000; } def decks as list of Deck init [ Deck{name: "@jennifer/scheduler", downloads: 4210, tags: ["cron", "jobs"]}, Deck{name: "@acme/labels", downloads: 87, tags: ["print"]}, Deck{name: "@jennifer/ledger", downloads: 1904, tags: ["finance", "csv"]} ]; # A method's bare name is the func value the filter takes. def top as list of Deck init lists.filter($decks, popular); for (def d in $top) { io.printf( "%s|pad=20 %d|pad=6|group=3|sep=, %s\n", $d.name, $d.downloads, strings.join($d.tags, ", ")); } io.printf("%s\n", json.encode($top));jennifer run 02-data.j
@jennifer/scheduler 4,210 cron, jobs @jennifer/ledger 1,904 finance, csv [{"name":"@jennifer/scheduler","downloads":4210,"tags":["cron","jobs"]},{"name":"@jennifer/ledger","downloads":1904,"tags":["finance","csv"]}] -
3 / 10
A JSON service
Routes bind to plain methods. Each request is served in its own spawned worker.
03-web.j 28 lines use json; import "web.j" as web; # Handlers are ordinary methods, handed to the router as # func values. Each request runs in its own spawned worker. func showDeck(ctx as web.Context) { def out as json.Value init json.map(); $out = json.set($out, "/deck", web.param($ctx, "name")); $out = json.set($out, "/registry", "registry.jennifer-lang.dev"); web.sendJson($ctx, 200, $out); } func health(ctx as web.Context) { web.text($ctx, 200, "ok\n"); } # Middleware returns true to continue to the route handler. func stamp(ctx as web.Context) { web.setHeader($ctx, "X-Powered-By", "jennifer"); return true; } def app as web.App init web.new(); $app = web.before($app, stamp); $app = web.get($app, "/healthz", health); $app = web.get($app, "/decks/:name", showDeck); web.run($app, "127.0.0.1:8080");curl -i localhost:8080/decks/scheduler
HTTP/1.1 200 OK Content-Type: application/json X-Powered-By: jennifer Date: Fri, 21 Aug 2026 00:23:30 GMT Content-Length: 60 {"deck":"scheduler","registry":"registry.jennifer-lang.dev"} -
4 / 10
Tasks and channels
Launch work with spawn, collect it with task, stream it over a channel. Nothing is shared, so nothing races.
04-concurrency.j 33 lines use io; use task; use channel; func square(n as int) { return $n * $n; } # spawn deep-copies its scope at launch, so there is no # shared memory to race on - and no lock to forget. def jobs as list of task of int init []; for (def i in 1..5) { def job as task of int init spawn { return square($i); }; $jobs[] = $job; } def total as int init 0; for (def t in $jobs) { $total = $total + task.wait($t); } io.printf("1..4 squared sums to %d\n", $total); # Channels stream values; each one is copied on send. def ch as channel of string init channel.make(0); def worker as task of int init spawn { channel.send($ch, "finished"); channel.close($ch); return 0; }; io.printf("the worker says: %s\n", channel.recv($ch)); task.wait($worker);jennifer run 04-concurrency.j
1..4 squared sums to 30 the worker says: finished
-
5 / 10
A described API
Who may call a route, with which scope, and what the body has to satisfy - declared per route and enforced by one guard.
08-webapi.j 51 lines use json; import "web.j" as web; import "webapi.j" as webapi; import "validate.j" as validate; # The authenticator turns a bearer token into an identity. How a token is # checked stays your business - the module never learns. func verifyToken(token as string) { if ($token == "admin-token") { return webapi.Identity{ok: true, subject: "u-admin", display: "admin", scopes: ["publish"]}; } def anonymous as webapi.Identity; return $anonymous; } func showDeck(ctx as web.Context) { webapi.sendJson($ctx, 200, json.set(json.map(), "/deck", web.param($ctx, "name"))); } func publish(ctx as web.Context) { def who as webapi.Identity init webapi.identity($api, $ctx); def form as map of string to string init webapi.validated($api, $ctx); def out as json.Value init json.set(json.map(), "/published", $form["tag"]); webapi.sendJson($ctx, 201, json.set($out, "/by", $who.subject)); } # One shim binds the Api into web's middleware chain: there are no closures yet. func apiGuard(ctx as web.Context) { return webapi.guard($api, $ctx); } def api as webapi.Api init webapi.new(); $api = webapi.mount($api, 1, "/v1"); $api = webapi.authenticator($api, verifyToken); $api = webapi.get($api, "/deck/:name", showDeck, webapi.public()); # A route's contract is declarative: who may call it, with which scope, and # what the body has to satisfy. The guard enforces all three. def publishing as webapi.Spec init webapi.Spec{ summary: "publish a deck version", auth: webapi.Auth.Bearer, scopes: ["publish"], rules: {"tag": [validate.required(), validate.maxLen(16)]}, rateLimit: 30, produces: webapi.Produces.Json }; $api = webapi.post($api, "/publish", publish, $publishing); def app as web.App init web.new(); $app = webapi.install($api, $app, apiGuard); web.run($app, "127.0.0.1:8081");curl localhost:8081/v1/... (four calls)
GET /v1/deck/scheduler -> 200 {"deck":"scheduler"} POST /v1/publish (no token) -> 401 {"error":"missing bearer token"} POST /v1/publish (no tag) -> 422 {"error":"invalid request","failures":[{"field":"tag","rule":"required","message":"is required"}]} POST /v1/publish (admin) -> 201 {"published":"v2.1.0","by":"u-admin"} -
6 / 10
Signing, sealing, stretching
Ed25519 signatures, AES-256-GCM sealing and PBKDF2 key derivation, with the nonce handled for you.
09-crypto.j 35 lines use io; use crypto; use convert; def manifest as bytes init convert.bytesFromString("scheduler 1.4.0", "utf-8"); # Ed25519: sign with the private half, verify with the public half alone. def keys as crypto.Keypair init crypto.signKeypair(); def signature as bytes init crypto.sign($keys.private, $manifest); io.printf("signature ok %t\n", crypto.verify($keys.public, $manifest, $signature)); def tampered as bytes init convert.bytesFromString("scheduler 1.4.1", "utf-8"); io.printf("tampered rejected %t\n", not crypto.verify($keys.public, $tampered, $signature)); # A passphrase is low entropy, so it is stretched before it becomes a key. def salt as bytes init crypto.randBytes(16); def phrase as bytes init convert.bytesFromString("correct horse battery staple", "utf-8"); def key as bytes init crypto.pbkdf2($phrase, $salt, 200000, 32, "sha256"); # AES-256-GCM. The nonce is generated and prepended for you - one less thing # to get catastrophically wrong. def sealed as bytes init crypto.encrypt($key, $manifest); def opened as bytes init crypto.decrypt($key, $sealed); io.printf( "sealed %d bytes -> opened \"%s\"\n", len($sealed), convert.stringFromBytes($opened, "utf-8")); # Opening with the wrong key is an authentication failure, not garbage. try { def wrong as bytes init crypto.decrypt(crypto.randBytes(32), $sealed); io.printf("unreachable\n"); } catch (e) { io.printf("wrong key %s\n", $e.message); }jennifer run 09-crypto.j
signature ok true tampered rejected true sealed 43 bytes -> opened "scheduler 1.4.0" wrong key crypto.decrypt: authentication failed (wrong key or tampered ciphertext)
-
7 / 10
Fitting a line, two ways
Fit a line by solving the normal equations with linalg, then check it against stats - two built-in libraries, the same answer.
10-science.j 45 lines use io; use linalg; use stats; # Eight builds: modules touched, and the seconds each one took. def modules as list of float init [18.0, 15.0, 24.0, 19.0, 16.0, 22.0, 17.0, 21.0]; def seconds as list of float init [10.2, 9.8, 11.1, 10.4, 9.9, 10.8, 10.1, 10.6]; # Fit seconds = a + b*modules the linear-algebra way: build the design matrix, # then solve the normal equations (A^T A) x = A^T y. def design as list of list of float init []; for (def m in $modules) { $design[] = [1.0, $m]; } def at as list of list of float init linalg.transpose($design); def lhs as list of list of float init linalg.matmul($at, $design); def rhs as list of float init linalg.matmul($at, $seconds); def fit as list of float init linalg.solve($lhs, $rhs); io.printf("normal equations a=%f|prec=4 b=%f|prec=4\n", $fit[0], $fit[1]); # The stats library reaches the same line by its own route. def ols as stats.Regression init stats.linearRegression($modules, $seconds); io.printf( "stats regression a=%f|prec=4 b=%f|prec=4 r2=%f|prec=4\n", $ols.intercept, $ols.slope, $ols.r2); # The identities hold, to floating-point precision. def residual as list of list of float init linalg.sub( linalg.matmul($lhs, linalg.inverse($lhs)), linalg.identity(2)); io.printf( "det=%f|prec=1 norm(A A^-1 - I)=%f|sci=true|prec=1\n", linalg.determinant($lhs), linalg.norm($residual)); # A singular matrix raises rather than handing back nonsense. try { def singular as list of list of float init [[1.0, 2.0], [2.0, 4.0]]; io.printf("%v\n", linalg.inverse($singular)); } catch (e) { io.printf("singular %s\n", $e.message); }jennifer run 10-science.j
normal equations a=7.6243 b=0.1441 stats regression a=7.6243 b=0.1441 r2=0.9955 det=544.0 norm(A A^-1 - I)=7.1e-15 singular linalg.inverse: matrix is singular (not invertible)
-
8 / 10
Sum types and missing keys
A sum type that match must cover, and a missing key that raises instead of lying.
05-strict.j 38 lines use io; use maps; use convert; # An enum is a real sum type: a value is exactly one variant, and match must # cover every one of them or say else. def enum Reply { Ok{body as string}, Failed{code as int}, Silence }; func describe(r as Reply) { match ($r) { when Ok(o) { return "ok: " + $o.body; } when Failed(f) { return "failed with " + convert.toString($f.code); } when Silence { return "no answer at all"; } } } def replies as list of Reply init [Reply.Ok{body: "pong"}, Reply.Failed{code: 503}, Reply.Silence]; for (def r in $replies) { io.printf("%s\n", describe($r)); } # Strict at the boundary: a missing key raises instead of handing back a # silent null. Test for it, or catch it - never guess. def limits as map of string to int init {"rps": 50}; if (maps.has($limits, "rps")) { io.printf("rps = %d\n", $limits["rps"]); } try { io.printf("burst = %d\n", $limits["burst"]); } catch (e) { io.printf("caught: %s\n", $e.message); }jennifer run 05-strict.j
ok: pong failed with 503 no answer at all rps = 50 caught: map has no entry for key burst
-
9 / 10
A tool for an AI agent
Register a method as an MCP tool and serve it over stdio. The router is an allow-list.
06-mcp.j 24 lines use json; import "mcp.j" as mcp; # A tool an AI host (Claude, an IDE, an agent runtime) can call over MCP. # It is a plain method - the protocol plumbing is the module's problem. func deckInfo(args as json.Value) { def out as json.Value init json.map(); $out = json.set($out, "/deck", json.asString($args, "/name")); $out = json.set($out, "/registry", "registry.jennifer-lang.dev"); return $out; } def schema as json.Value init mcp.property( mcp.schema(), "name", "string", "the deck to look up", true); def server as mcp.Server init mcp.server("deck-lookup", "1.0.0"); $server = mcp.addTool($server, "deckInfo", "Look up a Jennifer deck", $schema, deckInfo); # Only registered handlers can run: the router is an allow-list. mcp.serveStdio($server);echo '{"method":"tools/call", ...}' | jennifer run 06-mcp.j
{"jsonrpc":"2.0","result":{"content":[{"type":"text","text":"{\"deck\":\"scheduler\",\"registry\":\"registry.jennifer-lang.dev\"}"}],"isError":false},"id":1} -
10 / 10
Parameterized SQL
A functional query builder: identifiers are allow-listed, values bind through placeholders.
07-sql.j 21 lines use io; import "orm.j" as orm; # Declare the mapping once - no reflection, no magic. The dialect decides how # placeholders are spelled. def decks as orm.Schema init orm.schema("decks", "id", orm.Dialect.Postgres); $decks = orm.column($decks, "id", orm.ColumnKind.Int); $decks = orm.column($decks, "name", orm.ColumnKind.String); $decks = orm.column($decks, "downloads", orm.ColumnKind.Int); # Every builder step returns a fresh query; nothing mutates behind your back. def q as orm.Query init orm.from($decks); $q = orm.where($q, "downloads", ">=", "1000"); $q = orm.orderBy($q, "downloads", "desc"); $q = orm.limit($q, 10); # Identifiers and operators are allow-listed, values bind through placeholders: # a hand-built literal cannot inject. def rendered as orm.Rendered init orm.toSql($q); io.printf("%s\n", $rendered.sql); io.printf("%d value bound, 0 strings concatenated\n", len($rendered.params));jennifer run 07-sql.j
SELECT * FROM decks WHERE downloads >= $1 ORDER BY downloads DESC LIMIT 10 1 value bound, 0 strings concatenated
Read the rest in the reference
Every library and module has a page of its own, and every builtin in the language is listed in one table.