emacs-lsp-booster: putting the JSON parsing outside Emacs
Emacs LSP performance booster
At a glance
- What is it?
- emacs-lsp-booster is a Rust wrapper that sits between Emacs and a language server, converting JSON into elisp bytecode in a separate process so the editor stops blocking, and it gets a large part of its speed from a trick you can read in one line of Rust.
- Who is it for?
- emacs-lsp-booster fits an Emacs user on lsp-mode or eglot whose editor stalls when a language server sends a large diagnostic payload, since the wrapper moves the expensive part into a separate process and keeps both IO directions from blocking.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Activity is slowing. The repository last received commits 6 months ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem it was built around
The project starts by naming the performance issues in lsp-mode, which it says are mostly the same for eglot. JSON parsing in Emacs is slow. The server may block on sending data to Emacs when the buffer is full, because Emacs is consuming the data too slowly. And similarly, Emacs may block while attempting to send data to the server, hence blocking the Emacs UI, because the server may be busy. Those are two different kinds of blocking, and they pull in opposite directions, which is why they are hard to fix with a single change. The project credits the maintainer of lsp-mode for both maintaining it and for the inspiration, and points out that the maintainer had already tried a native async non-blocking jsonrpc fork of Emacs. That fork performs well, but it requires modifications to the Emacs source code, seems unlikely to be merged upstream, and is difficult to maintain since it also needs a separate code path in lsp-mode.
The trick: JSON becomes elisp bytecode, not a list
The wrapper converts JSON messages from the server at high speed directly into elisp bytecode, in its text representation, for Emacs to read. The example in the project overview makes the transformation concrete, with a small JSON object becoming a vector expression that Emacs can read. The performance claim is about four times better message parsing in Emacs for large JSON objects, and the project is careful about why. Emacs still has to parse the text representation and interpret it into elisp objects. The gain comes from two things: parsing an elisp object is apparently better optimised and simpler in Emacs, and by using bytecode to construct objects the wrapper eliminates duplicated objects, such as a repeated JSON key in the example. That second point is the interesting one. A JSON parser has to build a fresh symbol for every key it sees, and a message with a thousand diagnostics repeats a small set of keys thousands of times. Constructing the objects as bytecode means the shared parts are shared, which is why the approach pays off more as the payload grows.
Two threads, two buffers, no blocking
The second half of the design handles the IO problem, and it is simpler. The wrapper separates reading and writing into different threads and keeps pending messages in internal buffers within each thread, to avoid blocking on IO. Reading from the server and writing to the server no longer sit on the same path, so a slow consumer in Emacs can no longer stall the server, and a busy server can no longer stall the Emacs UI. The overall claim is that this wrapper strategy achieves a similar result to the native async non-blocking jsonrpc approach without requiring modifications to the Emacs source code. That is the trade the project is proposing: you do not get a patched Emacs, and you do get the same effect. The implementation is Rust, with serde and serde_json for the JSON side, clap for the command line, env_logger and log for output, anyhow for error handling, smallvec for small vectors and which for executable lookup.
Only stdio servers can be wrapped
There is one hard limit and the project flags it as important. At present only local lsp server programs which communicate by standard input and output can be wrapped, not servers communicating over network ports, whether local or remote. This is the direct consequence of the architecture: the wrapper is a process that the editor launches and talks to over pipes, so a server that wants to open a socket is a server the wrapper cannot sit in front of. For most local development setups the language server is already stdio-based, so this covers the common case. For anyone whose server is configured to listen on a port, the booster simply does not apply. It is also worth noting that the wrapping happens at the command level, which is why the configuration section has to advise the JSON parsing function in lsp-mode or eglot to be updated as well.
Wrapping the server command and patching the parser
Using the tool takes two steps. The first is to wrap the language server command with the emacs-lsp-booster executable, so that if the original command was pyright-langserver --stdio, you configure lsp-mode or eglot to run emacs-lsp-booster with that command appended instead. The second step is the awkward one: you have to advise or update the JSON parsing function in lsp-mode or eglot so that it parses any bytecode input seen, prior to parsing it as JSON. Both halves are needed. The wrapper writes bytecode instead of JSON, and unless the editor's parser knows to look for the bytecode marker first, it will choke. The project links to a separate eglot configuration repository by jdtsmith, and for lsp-mode it points at the plist deserialization advice in the lsp-mode documentation, which the configuration snippet also checks for.
The lsp-mode configuration, which is mostly advice
The lsp-mode setup is a block of Elisp to add to init.el, and it does two things. The first piece advises the JSON parse function so that it tries to read bytecode when the next character is the bytecode marker, and falls back to the original function otherwise. The second piece advises the function that resolves the final command, so that the booster is prepended to the server command, but only under four conditions: not during a test, not in a remote file, only when lsp-use-plists is on, and only when the native json-rpc connection function is not defined. Those conditions are the safety checks, and the comments in the snippet explain them, the file-remote-p one referring to lsp-resolve-final-command adding an extra shell wrapper for remote files. There is also a note that you should not use the native-jsonrpc custom version of Emacs while using this. The parse advice is the part that matters, and it is short.
(defun lsp-booster--advice-json-parse (old-fn &rest args)
"Try to parse bytecode instead of json."
(or
(when (equal (following-char) ?#)
(let ((bytecode (read (current-buffer))))
(when (byte-code-function-p bytecode)
(funcall bytecode))))
(apply old-fn args)))The logic is that if the next character in the buffer is the byte code marker, it reads the object and, if that object is byte code, calls it, which evaluates the construction of the message and returns the real value. Otherwise it falls through to the original parse function, so the advice degrades gracefully.
Getting the binary and confirming it is in the path
For Linux and Windows users, the project points to prebuilt binaries on the release page, with the caveat that the macOS binary there lacks proper code signing for now. Nix users have a flake maintained separately. The alternative route is crates.io, and it needs two steps: set up a Rust toolchain, then run cargo install emacs-lsp-booster, making sure the cargo bin folder is in your PATH. The PATH detail matters more than it sounds, because the lsp-mode advice checks for the executable with executable-find before it will prepend anything, so a booster that is installed but not on the PATH is silently never used. To verify that it is working, the project gives two checks: confirm that an emacs-lsp-booster process is running, and look in the stderr buffer, which for lsp-mode is a buffer named after the server, and for eglot has a leading space in its name, where it should contain an emacs_lsp_booster related log line. For more options, run emacs-lsp-booster --help.
What the repository contains and where it stands
The Rust project is laid out in the usual Cargo shape, with src/ for the implementation, tests/ for the test suite, and an examples/ directory holding a single example called native-json-parser, declared in the manifest as a cdylib so it can be loaded as a native module, which is the experiment in reading JSON outside Emacs. The dev-dependencies include the emacs crate at version 0.18 and tempfile, so the tests can drive a real Emacs. The package metadata declares the project at version 0.2.1, edition 2021, licensed MIT, and categorised under command-line-utilities and development-tools. The release history is short, with v0.2.1 on 2024-04-17, v0.2.0 on 2024-01-14 and v0.1.1 on 2024-01-06, and the last push to the repository was on 2026-04-12. The project also has a build and test workflow and a release workflow.
Editorial conclusion
emacs-lsp-booster fits an Emacs user on lsp-mode or eglot whose editor stalls when a language server sends a large diagnostic payload, since the wrapper moves the expensive part into a separate process and keeps both IO directions from blocking. It does not fit a setup using a server that talks over a network port rather than standard input and output, because the project states that only local servers communicating by standard input and output can be wrapped, and it does not fit someone on the native-jsonrpc build of Emacs, which the configuration notes explicitly tell you not to use. Before you wire it in, read the lsp-mode plist deserialization advice the project points at, since the second half of the configuration depends on it, and verify the win on your own buffers rather than trusting the reported four times figure for large JSON objects.
Frequently asked questions
What does emacs-lsp-booster actually do?
It provides a wrapper executable around language server programs. It converts JSON messages from the server at high speed directly into elisp bytecode in text representation for Emacs to read, and it separates reading and writing into different threads with pending messages in internal buffers, which avoids blocking on IO in either direction.
How do I install emacs-lsp-booster?
Linux and Windows users can download a prebuilt binary from the release page, though the macOS binary there lacks proper code signing. Nix users have a flake. Otherwise install it from crates.io by setting up a Rust toolchain and running cargo install emacs-lsp-booster, then make sure the cargo bin folder is in your PATH.
Can emacs-lsp-booster work with a language server that listens on a port?
No. The project states that at present only local lsp server programs which communicate by standard input and output can be wrapped, not servers communicating over network ports, whether local or remote.
How do I confirm emacs-lsp-booster is actually being used?
Check that an emacs-lsp-booster process is running, and look in the stderr buffer, which for lsp-mode is named after the server and for eglot has a leading space in its name, where it should contain an emacs_lsp_booster related log message. Running emacs-lsp-booster --help lists the advanced options.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/blahgeek-emacs-lsp-booster)Was this useful?