Official Demo · Project Page · Change Log
Verwandlung Online Judge is a self-hostable, open-source online judge for competitive programming. It presents algorithmic problems, accepts submissions in multiple languages, then compiles, runs and evaluates them against predefined test cases, reporting correctness, run time and memory usage.
Highlights:
- Cross-platform native judging. Submissions are judged natively on Linux and Windows through JNI, rather than relying on a Linux-only runtime.
- Sandboxed execution. On Linux each submission runs under kernel-enforced resource limits, a private network namespace and a seccomp syscall filter, as an unprivileged user; on Windows it runs under a separate low-privilege account inside a Job Object that caps the process count and enforces resource limits.
- Real-time feedback streamed to the browser with Server-Sent Events (SSE), so results appear progressively as each test case finishes.
- Horizontally scalable judging. The web app and the judgers are decoupled through an Apache ActiveMQ queue, so you can run as many judgers in parallel as you need.
- Contests and internationalization (English and Chinese) out of the box.
It is built on Spring Boot with the MyBatis persistence framework.
Verwandlung has two components, the web application and one or more judgers, communicating over ActiveMQ:
The fastest way to stand up the whole stack (database, message queue, web app and a judger) — two equivalent options:
git clone https://github.com/hzxie/voj.git
cd voj
# A) Docker Compose
docker compose up -d
# B) Plain `docker run`, no compose
scripts/run-docker.shBoth bring up the voj.web and voj.judger containers and persist the database
in a named volume, so the web UI is available at http://localhost:8080/voj and
your data survives image updates. run-docker.sh also generates strong random
secrets and prints them once; to run the prebuilt Docker Hub images instead of
building locally, use VOJ_PULL=1 scripts/run-docker.sh (or docker compose up,
which pulls by default).
Configuration (database, mail, URLs, the judger token) is read from VOJ_*
environment variables at runtime, so you can reconfigure or sit behind a reverse
proxy without rebuilding. See docker/README.md for the full
configuration, persistence and reverse-proxy details.
Web application produces a self-contained executable JAR (embedded Tomcat):
cd web
mvn package -DskipTests # -> web/target/voj.web.jar
java -jar target/voj.web.jarJudger builds a Spring Boot jar plus the JNI native library, so a C++ toolchain is required:
# Linux build prerequisites: g++, make and libseccomp
sudo apt-get install -y g++ make libseccomp-dev
cd judger
mvn package -DskipTests # -> judger/target/voj.judger.jarOn some JDK layouts the JNI build needs
jni_md.h/jawt_md.hcopied from$JAVA_HOME/include/linux(or.../win32) up into$JAVA_HOME/include.
Convenience wrappers live in scripts/: build-jars.sh,
build-docker.sh, run-web.sh and run-judger.sh (the run scripts preflight
MySQL/ActiveMQ and import sql/schema.sql, sql/seed.sql and sql/demo.sql on
first launch).
Both components read a voj.properties file (database, mail, message queue,
sandbox, etc.). See docs/configuration.md for the full
property reference.
| Component | Requirement |
|---|---|
| Java | JDK 17+ (tested through JDK 25) |
| Database | MySQL 8.0+ or MariaDB 10.4+ |
| Message queue | ActiveMQ 6.0+ (Jakarta JMS) |
| Judger (Linux build) | GCC/g++, make, libseccomp |
| Judger OS | Linux or Windows |
Untrusted submissions run inside a sandbox so they cannot harm the judging host:
- Linux (native). Each submission runs in a forked child with
setrlimit(CPU time, output size and process-count caps), accurate CPU-time and peak-memory accounting viawait4/getrusage, a private network namespace, a seccomp syscall filter (blockingptrace,mount, module loading, etc.) and privileges dropped to an unprivileged user (system.username, defaulting tonobody). If the judger runs as root and cannot drop privileges, the submission is refused rather than run as root. - Linux (isolate). Optionally set
judger.sandbox = isolateto delegate to the external isolate tool instead. - Windows (native). Each submission runs under a separate low-privilege Windows
account (
CreateProcessWithLogonW) inside a Job Object that caps the process count (anti fork-bomb), enforces memory and wall-clock limits, applies UI restrictions (no access to the interactive desktop or clipboard), and kills the whole process tree when the run ends.
The native sandbox does not provide full filesystem isolation. Reference answers
and configuration are protected by file permissions (the work directory is handed
to the sandbox user, while the checkpoint directory stays readable only by the
judger), but a submission can still read world-readable host files. For complete
filesystem isolation, run the judger as root with judger.sandbox = isolate, which
confines each submission to its own mount namespace.
For shared, multi-tenant hosts, create a dedicated unprivileged account for
system.username instead of reusing nobody. See
docs/deployment.md for the filesystem layout and
permissions a root deployment expects.
In 2011, LinkedIn released the Kafka message queue, named after the author Franz Kafka. A year later, Alibaba introduced MetaQ, built on Kafka, a nod to Kafka's novella Metamorphosis. Since the message queue is central to this application, it took the name "Verwandlung", the German title of Metamorphosis.
Contributions are welcome:
- Report bugs and request features on the issues page.
- For new features, please open an issue to discuss before sending a pull request.
- Translators for additional languages are especially appreciated.
This project is open-sourced under the GNU GPL v3.