jennifer programming language

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. 1 / 10

    Hello, and a library call

    Declarations are bare, uses carry a sigil, and every library is asked for by name.

    content/examples/01-hello.j

    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. 2 / 10

    Structs, lists, JSON

    Typed structs, a higher-order filter over a func value, and JSON out - no annotations, no reflection.

    content/examples/02-data.j

    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. 3 / 10

    A JSON service

    Routes bind to plain methods. Each request is served in its own spawned worker.

    content/examples/03-web.j

    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. 4 / 10

    Tasks and channels

    Launch work with spawn, collect it with task, stream it over a channel. Nothing is shared, so nothing races.

    content/examples/04-concurrency.j

    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. 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.

    content/examples/08-webapi.j

    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. 6 / 10

    Signing, sealing, stretching

    Ed25519 signatures, AES-256-GCM sealing and PBKDF2 key derivation, with the nonce handled for you.

    content/examples/09-crypto.j

    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. 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.

    content/examples/10-science.j

    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. 8 / 10

    Sum types and missing keys

    A sum type that match must cover, and a missing key that raises instead of lying.

    content/examples/05-strict.j

    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. 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.

    content/examples/06-mcp.j

    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 / 10

    Parameterized SQL

    A functional query builder: identifiers are allow-listed, values bind through placeholders.

    content/examples/07-sql.j

    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.

The cheatsheet