scalachess is the immutable chess rules engine behind lichess
Chess API written in scala. Immutable and free of side effects.
At a glance
- What is it?
- scalachess is the Scala 3 chess library that lichess.org is built on, written to be entirely functional, immutable and free of side effects. The repository ships the rules core, rating and tiebreak modules, a test kit, and a JMH benchmark suite that can produce JSON results.
- Who is it for?
- Scala teams that need chess rules, rating or tiebreaking logic should use scalachess, because the library is the same one lichess runs in production and its functional, immutable API makes positions safe to share and analyse concurrently. It is the wrong choice if you want a mutable board object you can move pieces on, and it is the wrong choice for a Java or Python project, since the code is Scala 3 with an sbt build.
- 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?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Scala, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The engine under lichess.org
scalachess is a chess API written in Scala for lichess.org, and the README's description of its design is the whole point: it is entirely functional, immutable, and free of side effects.
That constraint shapes everything about how you use it. A position is a value. Applying a move returns a new value rather than mutating the old one, so two analyses of the same position cannot interfere with each other, and a position can be passed around, stored and compared without any defensive copying. For a server that runs many games and many searches at once, that is not a stylistic preference but the property that makes the code correct by construction.
The topics confirm the implementation approach: bitboard and bitboard-datastructure alongside chess, functional-programming, lichess and scala-3. The library is Scala 3, and the licence is MIT, so there is no obstacle to using it in a commercial product.
The audience is specific. This is not a beginner's tutorial library and it is not a chess GUI. It is the rule engine of a large online chess platform, published so that others can use it: the people writing analysis tools, the people building a server that needs legal move generation, and the researchers who want a correct and fast implementation of the game rather than a simplified one.
The last push was on 2026-10-18, the repository is not archived, and the recent releases are 17.17.0 on 2026-09-05, 17.17.1 on 2026-09-09 and 18.0.0 on 2026-10-05.
Four directories of behaviour, not one library
The repository listing shows the project split into pieces that matter for anyone deciding what to depend on.
There is a core directory, which is the rules engine, and next to it a rating directory and a tiebreak directory, which are the rating systems and the tiebreak rules. Those are separate pieces of chess domain logic, and they are separate modules in the tree, which means a consumer that only needs legal move generation does not have to take the rating system with it.
The bench directory holds the JMH benchmark suite, the test-kit directory holds a reusable test harness, and the project directory carries the sbt build definition along with playJson, which is the JSON play format, and a rating and tiebreak test area. There is a jitpack.yml, which is how the project is published for JVM consumers who do not want to use a full sbt build, and a Pipfile with a Pipfile.lock for the Python tooling, plus a flake.nix and flake.lock for a Nix development environment.
The sync-openings.py script at the root is worth a note, because it tells you something about the shape of the project. Keeping an opening database synchronised is a data problem, not a Scala problem, and the library needed a tool for it that lives outside the JVM build. That is a reasonable division of labour, and it is also a reminder that the repository is not only the library: it carries the development environment, the benchmarks and the data tooling that the maintainers need.
The .scalafmt.conf and .scalafix.conf files, together with the prepare target in the build instructions, show that formatting and lint rules are enforced rather than suggested.
Getting the sources and running the sbt build
Installation is a clone and an sbt session. The README is explicit that binary releases are not the path here: you clone scalachess and start sbt in the scalachess directory.
git clone https://github.com/lichess-org/scalachesssbtOnce inside the sbt shell, the commands are short. To compile scalachess you run:
compileand to run the tests:
testKit / testThe scoped form matters here. testKit is the test kit module, so testKit / test runs the suite that exercises the library through the reusable harness rather than the unit tests inside each module.
Formatting and linting are handled by the same shell, and the README points at scalafmt and scalafix documentation for installation:
sbt prepareFor Python code the setup is separate and, again, deliberately minimal. The README asks you to install pipenv and then run pipenv install from the project root, which reads the Pipfile and Pipfile.lock. That is the only Python dependency management in the project, and it exists for the tooling rather than for the library.
So the development loop is sbt for Scala, pipenv for the Python helpers, and a make-style set of commands inside the sbt shell for everything else.
The JMH benchmark suite and its four run modes
The benchmark section is the most detailed part of the README, and it is worth reading because a chess engine is exactly the kind of code where a naive implementation passes every test and still loses a search.
To run the full benchmarks, which the README says takes more than 1 hour to finish:
bench / Jmh / runTo output a json file:
bench / Jmh / run -rf jsonThere are then two shorter modes. The quick one is for a smoke test, and the README warns that results may be inaccurate:
bench / Jmh / run -i 1 -wi 1 -f1And the longer one, with more iterations, more warmup iterations and more forks:
bench / Jmh / run -i 3 -wi 2 -f2Finally, a run for one benchmark class only, which is the mode you would use when you have changed a specific data structure and want to see only that number:
bench / Jmh / run -rf json .*PlayBench.*The JMH flags here are the standard ones, and the README maps them to what they mean: iterations, warmup iterations and forks. The one that matters for a self-hosted runner is the warmup count, because a single warmup iteration and a single fork is a configuration that will show you a number rather than a measurement, which is why the README labels it as results may be inaccurate.
The json output is the other half of the story, since a machine readable result is what a continuous integration job or a performance regression tracker can consume. The PlayBench example suggests the benchmarks are organised around play generation, which is the hot path of any chess server.
Releases are a manual three-step ritual
The release section is three steps, and it is worth knowing because it tells you what a version tag means.
gh release create X.Y.ZThe full sequence is to bump the version in build.sbt, commit the version bump and push to master, and then create the release with the GitHub CLI. The default branch is master, so the flow is trunk based with the tag created afterwards rather than from a release branch.
The consequence for a consumer is that a release tag is a snapshot of master at a moment, and that the version in build.sbt is the single source of truth for it. There is no changelog file in the repository listing to consult, so the release history on GitHub and the commit log are the record of what changed.
The version numbers themselves are informative. The project was on 17.17.0 and 17.17.1 in September 2026 and moved to 18.0.0 on 2026-10-05, so the minor version is doing real work, presumably for breaking changes to the API, while patch releases carry fixes. For a library with an immutable functional core, a major version is a signal that types moved, and the reason the design is immutable and side-effect free is precisely that such a change is a compile-time decision rather than a runtime surprise.
The jitpack.yml file at the root means the artifact is also consumable from JitPack, which is how a JVM project without an sbt build can depend on it.
Where scalachess is the wrong tool
The limits are a consequence of what the project is and who it serves.
First, it is Scala 3. The library is published through JitPack and sbt, and a Java or Python team would have to call it through a service rather than link it. That is fine for a platform team and wrong for a desktop tool.
Second, the functional model has a cost that is worth stating plainly. An immutable, side-effect free API means a move produces a new position, and a search that explores millions of positions allocates a great deal. The design is correct and safe, and it is not free, and a project that needs a mutable board for a tight inner loop will find the API fights it.
Third, the library is a rules engine plus rating and tiebreak logic. It is not an analysis suite, not a puzzle database, not an opening book, and not an engine search frontend. The README does not describe any of those, and the repository layout matches: core, rating, tiebreak, bench, test-kit, and the JSON play format.
Fourth, the documentation in the README is operational rather than conceptual. It tells you how to compile, test and benchmark, and points at the project site for anything else, so learning the API means reading the source or the site rather than reading a manual.
For a team that needs correct, fast, side-effect free chess rules in Scala, and is willing to depend on the exact library lichess itself runs, this is a good deal. For anything else, the size of the domain makes it a heavy dependency.
Editorial conclusion
Scala teams that need chess rules, rating or tiebreaking logic should use scalachess, because the library is the same one lichess runs in production and its functional, immutable API makes positions safe to share and analyse concurrently. It is the wrong choice if you want a mutable board object you can move pieces on, and it is the wrong choice for a Java or Python project, since the code is Scala 3 with an sbt build. Before adopting, check the module you need in the repository layout, because the rating, tiebreak and core pieces are separate, and note that the release process is manual, with a version bump in build.sbt and a gh release create step.
Frequently asked questions
How do I build and test scalachess?
Clone the repository, start sbt in the scalachess directory, then run compile to build the library and testKit / test to run the tests through the test kit module. The README also documents sbt prepare for running scalafmt and scalafix.
How do I run the scalachess benchmarks?
Inside the sbt shell, bench / Jmh / run executes the full suite, which the README says takes more than an hour. Adding -rf json writes a JSON file, and -i 1 -wi 1 -f1 gives a quick run whose results may be inaccurate, while -i 3 -wi 2 -f2 runs longer.
What does scalachess guarantee about its design?
The README states that it is entirely functional, immutable, and free of side effects, and that it is a chess API written in Scala for lichess.org. The topics record a bitboard data structure and functional programming.
How is a new scalachess version released?
Bump the version in build.sbt, commit the version bump and push to master, then create the release with gh release create X.Y.Z. The project publishes to JitPack as well, with a jitpack.yml at the repository root.
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/lichess-org-scalachess)Was this useful?