Babashka 1.13.220 adds FFI for calling native libraries
Babashka 1.13.220 adds an FFI built on Java's Panama API, so a Clojure script can call a native library without writing a pod or any Java.
3 min read

Babashka, the fast-starting Clojure scripting runtime, can now call native C libraries directly. Version 1.13.220 shipped on 31 August 2026 with a foreign function interface, and author Michiel Borkent explained the design on his blog. Until now, reaching native code from a Babashka script meant writing a pod: a separate program that Babashka talks to over a socket. The new FFI removes that step.
A foreign function interface is the plumbing that lets one language call another. Here it lets Clojure call C.
What the new interface does
The implementation is built on java.lang.foreign, the Foreign Function and Memory API that arrived in the JDK through Project Panama. Borkent notes that the existing coffi library builds on the same foundation. So this is not a bespoke mechanism; it is the JVM's own supported route to native code, exposed to scripts.
The release covers more than simple function calls. According to the GitHub release notes, the work spans pull requests #2031 through #2077 and includes memory segments, arena management, struct and union layouts, and callbacks. Callbacks matter: they let a C library call back into your Clojure code.
Memory is the part that demands attention. Native memory sits outside the garbage collector, so Babashka makes you manage it explicitly through arenas. An arena owns a block of native memory and frees it when it closes. Clojure's with-open gives you that cleanup automatically at the end of a block. Get this wrong and you do not get an exception. You get a segfault.
A first call is small. Loading zlib and asking for its version returns the string 1.3.1.
Why the SQLite example matters
The example Borkent highlights is not a toy. The FFI lets you define a Clojure function and register it inside a native database, so a SQLite query can call it during execution.
That was not possible with pods. A pod runs in its own process, and SQLite cannot reach across a socket in the middle of evaluating a query. The function has to live in the same process as the database engine. Direct FFI puts it there.
This is the general shape of the change. Anything requiring your code and the native library to share one process and one memory space was previously out of reach. Callbacks, custom allocators and in-process extension points now open up.
A quieter change to Linux binaries
One line in the release affects people who never touch the FFI. On Linux, the default Babashka build has moved from fully static to mostly static, which means glibc is now linked dynamically. macOS and Windows builds already worked this way.
The reason is the FFI itself: loading arbitrary native libraries at runtime does not sit well with a fully static binary. The trade is portability. A fully static binary runs anywhere; a mostly static one expects a compatible glibc on the host. If you copy Babashka into a minimal container or an Alpine image, test it before you upgrade.
What this means for developers
If you already use Babashka for scripts and have been avoiding a task because it needed a C library, re-check that task. The pod workaround was real friction, and it is gone.
If you maintain a pod, this is worth reading closely. Pods still make sense for isolation and for wrapping large native SDKs. They no longer make sense purely to reach a handful of C functions.
If you are new to this, start with the arena rules rather than the call syntax. Calling zlibVersion is easy. Knowing which memory you own, and when it is freed, is what separates a working script from an intermittent crash. Wrap every arena in with-open until you have a reason not to.
And if you deploy Babashka in containers, check your base image for glibc before taking 1.13.220. That change ships whether or not you use the new interface.
Sources
- Babashka FFI - Michiel Borkent
- Babashka releases: v1.13.220 - GitHub
Related articles

TryNix runs any Nix package in a browser tab
TryNix boots a Linux kernel compiled to WebAssembly and runs any of 310,083 nixpkgs versions in a tab. Python 3 takes 7.5 seconds on a first visit.

Asahi Linux now officially supports M3 series Macs
Asahi Linux has merged M3, M3 Pro and M3 Max support into its installer. Wi-Fi, USB 3 and AV1 decoding work. Sleep, HDMI and fast 3D do not.

Kubernetes 1.37 promotes rootless mode to beta
Kubernetes 1.37 moves KubeletInUserNamespace to beta. The kubelet, container runtimes, CNI plugins and kube-proxy can now all run as a non-root user.
The daily brief
Three to five stories a day, and what each one means for the people who build software. Free, no spam.