Kontrakt++ is a DSL that turns your HTTP API tests from Ξv = v_e Β· ln(m0/mf) into actual working Java code, no PhD required ππ»
Kontrakt++ is a Domain-Specific Language (a fancy term for "mini-language that does one thing well") that lets you write API tests in a human-friendly format and then magically transforms them into real JUnit tests that actually run against your backend.
.test files (you write) β AST voodoo β GeneratedTests.java (JUnit) β β /β results
Kontrakt/
β
βββ backend/ # SpringBoot backend to test against
β
βββ bin/ # da "binaries" - export yard
β
βββ examples/ # Test files you can actually read
β βββ assignment.test # the example input given to us
β βββ example_1.test # 6 tests
β βββ example_2.test # this one is for experimenting
β
βββ lib/ # JAR files (the dependencies)
β
βββ public/ # this one's for the GitHub
β
βββ src/ # The brains of the operation
β βββ ast/ # Abstract Syntax Tree classes
β βββ CodeGenerator.java # I mean, the name π€·π»ββοΈ
β βββ Main.java # The main event
β βββ Parser.cup # Grammar rules (CUP)
β βββ Scanner.flex # Tokenizer rules (JFlex)
β
βββ Makefile # this is one of the coolest things ever (special section at the end)
βββ README.md
git clone https://github.com/notdulain/Kontrakt.git
cd Kontraktmake sure these files exist in lib/ (because even magic needs tools):
jflex-full-1.9.1.jar
java-cup-11b.jar
java-cup-11b-runtime.jar
junit-platform-console-standalone-1.10.1.jar
if they're missing, well... good luck with compiling π
run this before anything, to make sure you have a clean slate
make cleanmake generatewhat this does: runs JFlex and CUP to create Java code that understands your DSL. Think of it as teaching Java to speak the "test-language".
make compiletranslation: "please turn all this java code into something the computer can actually run (class files)"
make runWhat happens: Takes example_1.test (threre are more commands to test others!) and creates GeneratedTests.java. This is where the real magic happens!
cd backend
./mvnw spring-boot:runWindows ππ»
cd backend
.\mvnw spring-boot:runOption B: IntelliJ (jetbrains didn't develop the best IDE for java only for us to use the terminal π€π»)
- open the backend folder as a project in IntelliJ
- build the maven project (troubleshooting this is a whole other paradigm of programming imo)
- find
src/main/java/whatever/KontraktBackendApplication.java - look for the cute little green play button
βΆοΈ at the top - um, click it
- wait for "Started Application" in the console
just to verify, open http://localhost:8080 in your browser. if you see something other than an error, you're good to go!
make compile-teststhis compiles GeneratedTests.java with JUnit in the classpath.
make run-teststhe moment of truth: this runs your generated JUnit tests against the running backend.
make cleanmake allmake test-fullthis will:
- clean your directory
- compile everything you need to compile
- will parse your .test file
- compile the GeneratedTests.java
- and even run it against your backend using JUnit Jupiter
(this whole make-thing is pretty cool huh? stay tuned for the Makefile segment π½)
I put together a comprehensive note in Notion since I started working on this project... it has everything from understanding how a language parser works to the hair-pulling errors I encountered + how I solved them.
click the image for the treat π

Here's what you can test right now (assuming backend is running):
test Login {
POST "/api/login" {
body = "{ \"username\": \"$username\", \"password\": \"$password\" }";
}
expect status = 200;
expect header "Content-Type" contains "json";
expect body contains "\"success\": true";
expect body contains "\"token\":";
expect body contains "\"message\": \"Login successful\"";
}
test GetUser {
GET "/api/users/$user_id";
expect status = 200;
expect header "Content-Type" contains "json";
expect body contains "\"id\": 42";
expect body contains "\"username\": \"user42\"";
expect body contains "\"email\": \"[email protected]\"";
expect body contains "\"role\": \"USER\"";
}
test UpdateUser {
PUT "/api/users/$user_id" {
body = "{ \"role\": \"ADMIN\" }";
}
expect status = 200;
expect header "Content-Type" contains "json";
expect body contains "\"id\": 42";
expect body contains "\"updated\": true";
expect body contains "\"role\": \"ADMIN\"";
expect body contains "\"message\": \"User updated successfully\"";
}
test DeleteUser {
DELETE "/api/users/$delete_id";
expect status = 200;
expect header "Content-Type" contains "json";
expect body contains "\"id\": 99";
expect body contains "\"deleted\": true";
expect body contains "\"message\": \"User deleted successfully\"";
}
test GetNonExistentUser {
GET "/api/users/9999";
expect status = 404;
expect body contains "\"error\"";
expect body contains "\"User not found\"";
}
test LoginInvalidCredentials {
POST "/api/login" {
body = "{ \"username\": \"wrong\", \"password\": \"wrong\" }";
}
expect status = 401;
expect body contains "\"success\": false";
expect body contains "\"message\": \"Invalid credentials\"";
}
make test-assignmentmake test-example-2While our parser and code generator are getting all the spotlight, let's take a moment to appreciate the real MVP backstage: the Makefile. This humble text file is the stage manager that makes sure everyone hits their cues perfectly.
Think of it as your personal assistant that remembers all the annoying commands so you don't have to:
# instead of typing all this nightmare:
java -jar lib/jflex-full-1.9.1.jar -d src
java -jar lib/java-cup-11b.jar -destdir src -parser parser src/Parser.cup
mkdir -p bin
javac -cp "lib/java-cup-11b-runtime.jar:src" -d bin /*.java
java -cp "lib/java-cup-11b-runtime.jar:bin" Main examples/example_1.test
javac -cp "lib/junit-platform-console-standalone-1.10.1.jar:." GeneratedTests.java
java -jar lib/junit-platform-console-standalone-1.10.1.jar --class-path . --scan-class-path
# You just type:
make all + make test-fullmic. drop. π«³π»π€
Picture this: Bell Labs, 1977. A programmer named Stuart Feldman is staring at a massive C program that takes hours to compile. Constantly fixing one file but having to recompile everything because dependencies are a mess.
One day, after waiting 3 hours for a full rebuild...
read the full article (click) ππ»

β "Parser error on line X" Probably: you forgot a semicolon. yes, again π€¦π»ββοΈ (or forgot your own language's syntax)
β "Connection refused to localhost:8080" Solution: Your backend isn't running. See the "Running the Backend" section above.
β "GeneratedTests.java doesn't reflect my changes" Fix: Run make run again. The generator isn't psychic (yet).
β "Tests fail but curl works" Likely cause: Your test expectations don't match what the backend actually returns. Check those JSON strings!
If you see green checkmarks and passing tests, congratulations! You've successfully:
ποΈ Built a language parser from scratch
π Transformed DSL into executable Java code
π Tested a real HTTP API
π Looked cool doing it
now go forth and test all the APIs! or take a well-deserved coffee break, I won't judge πβ



