Open-source project
rohanpadhye/JQF avatar
rohanpadhye/JQF

JQF turns JUnit property tests into coverage-guided fuzzers for Java

JQF + Zest: Coverage-guided semantic fuzzing for Java.

744 stars117 forksJavaBSD-2-Clause

At a glance

What is it?
JQF is a feedback-directed fuzz testing platform for JVM bytecode that takes the property-based testing abstraction as its input model, so a fuzz driver is written as a parameterised JUnit test method. Its Zest algorithm biases generation toward inputs that are both structurally valid and semantically valid, which is how it reaches past the error-handling code that ordinary fuzzers stop at.
Who is it for?
Adopt JQF if you are testing Java code whose inputs have structure, XML, JavaScript, class files or SQL, because a random generator alone rarely reaches the interesting paths and a byte-mutation fuzzer cannot even parse the input. Skip it if you are fuzzing a parser that takes opaque bytes, since that is the case AFL and libFuzzer already handle.
Can I use it commercially?
Yes. BSD-2-Clause 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 93 days ago.
What is it written in?
Mainly Java, 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 input model is a generator, not a byte buffer

JQF is a feedback-directed fuzz testing platform for Java, described as AFL or libFuzzer but for JVM bytecode.

The interesting part is how it takes input. Instead of treating the input as a sequence of bytes, it uses the abstraction of property-based testing, which makes it nice to write fuzz drivers as parameteric JUnit test methods. Fuzz tests run under JUnit 4 or JUnit 5, and the test arguments are produced by a pluggable generator provider, junit-quickcheck by default. The engine itself depends on neither.

That last clause is a real design constraint rather than a detail. If the engine were tied to one generator library, swapping the generator would mean rewriting the engine, and the point of a modular framework is that the parts are replaceable.

The input model is spelled out in the generator section. A `Generator<T>` provides a method for producing random instances of type `T`, and a generator for type `Calendar` returns randomly-generated `Calendar` objects. The documentation then lists the example generators that exist: XML documents, JavaScript programs, JVM class files using BCEL, SQL queries, HTTP requests, and many more.

Those examples are the argument for the approach. A generator for a JavaScript program produces syntactically valid JavaScript by construction. A byte-mutating fuzzer handed a JavaScript buffer produces mostly parse errors, and the interesting code, the part that runs after the parse succeeds, never executes.

That problem has a name in the documentation, structure-aware fuzzing, and the README explains it by contrasting the two schools. Binary fuzzing tools like AFL and libFuzzer treat the input as a sequence of bytes, and if the test program expects highly structured inputs such as XML documents or JavaScript programs, mutating byte-arrays often results in syntactically invalid inputs, so the core of the test program remains untested. Structure-aware fuzzing tools use domain-specific knowledge of the input format to produce inputs that are syntactically valid by construction.

Zest adds semantic validity on top of syntactic validity

JQF supports the Zest algorithm, which uses code-coverage and input-validity feedback to bias a QuickCheck-style generator toward generating structured inputs that can reveal deep semantic bugs.

The two feedback signals are the mechanism, and they are extracted in specific ways. JQF extracts code coverage using bytecode instrumentation, and input validity using JUnit's assumption API, `org.junit.Assume` on JUnit 4 and `org.junit.jupiter.api.Assumptions` on JUnit 5. An input is valid if no assumptions are violated.

Using the assumption API for validity is the clever part, and it is what makes the approach practical in an existing test suite. Assertions in a test are already the place where you say what a valid input looks like, so if your code already assumes that a document is well-formed, that assumption is exactly the validity signal the fuzzer needs. No separate specification language.

The goal of Zest is stated plainly: to find deep semantic bugs that cannot be found by conventional fuzzing tools, which mostly stress error-handling logic only. That is the difference between reaching the branch that parses a nested structure and reaching the branch that gets the structure semantically wrong, such as a document with the right syntax and an impossible combination of fields.

The default invocation is simple. By default, JQF runs Zest via the command `mvn jqf:fuzz`, so the entry point is a Maven goal and the engine is driven from a normal build.

The research behind it is the ISSTA 2019 paper Semantic Fuzzing with Zest, by Rohan Padhye, Caroline Lemieux, Koushik Sen, Mike Papadakis and Yves Le Traon, and the project asks you to cite that paper if you reference the algorithm, with a separate citation for the JQF framework itself, Coverage-Guided Property-Based Testing in Java.

Five guidance front-ends sit behind the same engine

JQF is described as a modular framework, and the pluggable fuzzing front-ends are called guidances.

The first is binary fuzzing with AFL, with a tutorial linked. That is the escape hatch for the case where the input really is bytes, and it uses a small C shim. The repository root has a Makefile for exactly that, and it builds a single binary:

makefile
CC ?= clang

all: bin/afl-proxy

bin/afl-proxy: fuzz/src/main/c/afl-proxy.c
	$(CC) $(CFLAGS) $< -o $@ 

The second and main one is semantic fuzzing with Zest, with two tutorials, one general and one on fuzzing a compiler.

The third is complexity fuzzing with PerfFuzz, from the ISSTA 2018 paper, which targets inputs that are valid but computationally expensive rather than inputs that crash. The fourth is reinforcement learning with RLCheck, based on a fork of JQF, from the ICSE 2020 paper, which replaces the heuristic search with a learned policy. The fifth is mutation-analysis-guided fuzzing with Mu2, from the ISSTA 2023 paper, which uses mutation scores rather than coverage to steer generation.

The list is a good summary of where the field has gone since 2018. Zest is coverage plus validity. PerfFuzz adds a cost objective. RLCheck replaces the search heuristic. Mu2 changes the feedback signal from what was executed to what was changed.

All of them run through the same engine and the same JUnit execution model, which is why they are described as front-ends rather than separate tools.

The generator layer is pluggable in the same way. junit-quickcheck is the default provider, and other providers such as Instancio and jetCheck plug in through the `ArgumentsGeneratorFactory` SPI without changes to the engine. The repository layout confirms the module split, with jqf-core/, jqf-junit4/, jqf-junit5/, jqf-generator-quickcheck/, jqf-generator-instancio/ and jqf-generator-jetcheck/ as separate directories alongside the maven-plugin/ and the instrument/ code that does the bytecode instrumentation.

The example is a property test for a Patricia trie

The worked example in the documentation is a JUnit-Quickcheck test for checking a property of the PatriciaTrie class from Apache Commons Collections. The property being tested is that if a `PatriciaTrie` is initialised in a certain way, then something holds, and the example directory shows the shape of these projects.

A trie is a good choice for a first example because it has real structural constraints. The keys share prefixes, the value type is constrained, and the input space is a sequence of operations rather than a single document. Random bytes would be meaningless; a generator produces a sequence of valid insertions and lookups, and the property is what should hold for all of them.

The repository structure supports the same reading. There is an examples/ directory with a pom.xml and a fen.txt, plus a separate fuzz/ directory holding the C shim, an instrument/ directory for the bytecode instrumentation, jqf-core/ for the engine, the JUnit 4 and JUnit 5 integration modules, the three generator provider modules, a maven-plugin/ directory for the mvn jqf:fuzz goal, integration-tests/, and scripts/.

The example files list also shows that the examples are ordinary Maven projects, since examples/pom.xml is listed alongside the source tree. That is what makes the approach adoptable in an existing codebase: the fuzz driver is a test in your project, not a separate harness you have to wire up.

The project is Java, BSD-2-Clause licensed, with the last push on 2026-07-22. The release history shows three tags on the same day, jqf-3.0, jqf-2.1 and jqf-2.0, which reads as a version bump plus a backport rather than three separate features.

What the project has actually found

The README claims JQF has been successful in discovering a number of bugs in widely used open-source software, and it names them: OpenJDK, Apache Maven and the Google Closure Compiler.

Those three are worth reading as a category rather than as a list. All three are large Java codebases with complex input formats, which is exactly the profile the approach targets. OpenJDK takes class files and parses them, Apache Maven consumes POM and settings files, and the Closure Compiler consumes JavaScript. In each case, a byte-mutating fuzzer spends nearly all its time producing parse errors, and a property-based generator plus coverage feedback gets past the parser.

That is the shape of the claim the project makes, and the papers linked at the top of the README are the evidence for it. There are seven of them, spanning ISSTA 2018, ISSTA 2019 twice, ICSE 2020, ASE 2020, ICSE 2021 and ISSTA 2023, which is the research trajectory of the whole line: PerfFuzz first, then Zest, then the JQF framework, then the forks and extensions.

The ASE 2020 paper is BigFuzz, and ICSE 2021 is Bonsai, and both of those are referenced in the topic list of the project, along with junit, property-based-testing, quickcheck, coverage-guided-fuzzing and fuzzing itself.

For an evaluator, the honest framing is that JQF is a research platform with a strong publication record rather than a fuzzing tool with a support commitment. The claims about found bugs are the project's own, and the papers are where you would go to check them.

Editorial conclusion

Adopt JQF if you are testing Java code whose inputs have structure, XML, JavaScript, class files or SQL, because a random generator alone rarely reaches the interesting paths and a byte-mutation fuzzer cannot even parse the input. Skip it if you are fuzzing a parser that takes opaque bytes, since that is the case AFL and libFuzzer already handle. Verify first that your inputs can be expressed as a generator rather than as a raw buffer, that the code you want to reach is on the coverage path the algorithm is steering, and that you have JUnit 4 or JUnit 5 available, since the engine runs the tests as ordinary test methods.

Frequently asked questions

What is JQF and how is it different from AFL or libFuzzer?

JQF is a feedback-directed fuzz testing platform for JVM bytecode, and it uses the abstraction of property-based testing so fuzz drivers are written as parameteric JUnit test methods. AFL and libFuzzer treat the input as a sequence of bytes, which often produces syntactically invalid inputs for structured formats like XML or JavaScript and leaves the core of the program untested.

How does the Zest algorithm decide which inputs to keep?

It uses two feedback signals. Code coverage is extracted through bytecode instrumentation, and input validity is extracted through JUnit's assumption API, org.junit.Assume on JUnit 4 and org.junit.jupiter.api.Assumptions on JUnit 5. An input is valid if no assumptions are violated, and Zest biases the generator toward valid inputs that maximise coverage.

Which fuzzing algorithms can I use with JQF?

The pluggable front-ends, called guidances, are binary fuzzing with AFL, semantic fuzzing with Zest, complexity fuzzing with PerfFuzz, reinforcement learning with RLCheck and mutation-analysis-guided fuzzing with Mu2. The generator layer is separate: junit-quickcheck is the default provider, and Instancio and jetCheck plug in through the ArgumentsGeneratorFactory SPI without changes to the engine.

Official sources

  1. Issues
  2. License: BSD-2-Clause
  3. README
  4. Releases
  5. rohanpadhye/JQF on GitHub
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/rohanpadhye-jqf.svg)](https://hysenlabs.com/projects/rohanpadhye-jqf)

Was this useful?