EqualsVerifier: testing the equals and hashCode contract
EqualsVerifier can be used in Java unit tests to verify whether the contract for the equals and hashCode methods is met.
At a glance
- What is it?
- EqualsVerifier is an Apache 2.0 licensed Java library that checks, in an ordinary unit test, whether a class satisfies the contract for its equals and hashCode methods, and it is deliberately opinionated about it. The judgement is that the strictness is the product, and that the project is unusual in stating, in the readme, the reason a person thinks equality bugs matter.
- Who is it for?
- EqualsVerifier fits a Java team that has been bitten by an equals implementation that breaks the contract, a mutable field in the hash, an inheritance asymmetry, because a single line in a test now covers the whole set of rules the language defines. It is the wrong choice if you want a quiet test that only checks the obvious cases, since the library is strict by design and its simple mode is a deliberate relaxation rather than a second opinion.
- Can I use it commercially?
- Yes. Apache-2.0 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 received new commits within the last day.
- 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
A library for one contract, and one line of test
EqualsVerifier can be used in Java unit tests to verify whether the contract for the equals and hashCode methods in a class is met. That is the entire scope, and it is a scope that deserves a library.
The equals contract in Java is not a single rule but a set of them, and they interact. Equality has to be reflexive, symmetric, transitive and consistent, and it has to agree with null handling and with the hash code, and the hash code has to stay stable while the object is in a collection. A class that gets most of them right is the normal case, and the failure shows up as a bug in a hash map somewhere far from the class.
Adding the library is one test scoped dependency:
<dependency>
<groupId>nl.jqno.equalsverifier</groupId>
<artifactId>equalsverifier</artifactId>
<version>4.5.2</version>
<scope>test</scope>
</dependency>The readme notes that there is also a fat jar with no transitive dependencies, published under the artifact id equalsverifier-nodep, for projects that do not want a dependency tree in their test scope.
And using it is a single line in a test method:
import nl.jqno.equalsverifier.*;
@Test
public void equalsContract() {
EqualsVerifier.forClass(Foo.class).verify();
}
There is a video for people who would rather watch than read, made by Tom Cools, and the documentation is a mix of a project website, a changelog, a code of conduct and contribution guidelines.
The strictness is the point, and there is a lenient mode
EqualsVerifier is described as an opinionated library, which means that it can be quite strict, and the readme says what to do about it.
If you feel it is too much, you can make it more lenient:
import nl.jqno.equalsverifier.*;
@Test
public void equalsContract() {
EqualsVerifier.simple().forClass(Foo.class).verify();
}This way the library will throw fewer errors at you. But the readme immediately adds the advice that it is usually better to just fix the errors, because EqualsVerifier throws them for a reason.
That advice is the interesting part of the design. A checker that is easy to relax is a checker that gets relaxed, and then it stops finding the bug it was added for. The strict mode is the one that knows about the inheritance rules, the null handling, the consistency requirement, the hash code agreement, the final fields, the mutability, the subclass relationships and the caching of the hash code, and the lenient mode deliberately gives up most of that list.
The library is published to Maven Central with a javadoc badge, the project has an isitmaintained badge for the average issue resolution time, and the copyright notice covers 2009 to 2026, which puts the project's age in the same range as the language feature it guards.
The readme states why equality bugs matter beyond Java
There is a section in the readme called a note on equality, and it is the part of the project that is best read even if you never use the library.
The argument runs like this. EqualsVerifier cares about bug-free equality, in Java and in real life. The place where a person happens to be born, the colour of their skin, their gender, or the person they happen to love, must not affect the way they are treated in life. If it does, that is a bug and it should throw an error.
The last sentence is a fair description of what the library does, and the argument is about the same thing in both domains. A class that treats two instances as different because of a field that should not matter is making a judgement about equality that nobody asked for, and the same shape of mistake is what turns a piece of data into a decision about a person.
The instruction that follows is short: do not allow bugs in your equality.
The design implication for the library is that a failure is an exception rather than a warning. A strict checker that quietly logged its findings would be ignored within a week, and a library that only throws when the contract is genuinely broken is a library that can be trusted to stay in a build.
It is also why the strictness is not configurable in detail by default. The library's position is that the contract is the contract, and the escape hatch is a separate entry point, simple, that a team has to choose deliberately.
The build is split by Java version and by packaging variant
The module list in the repository is unusual and instructive, because it shows the shape of the release rather than the shape of the code.
There are three version specific modules, equalsverifier-21, equalsverifier-25 and equalsverifier-26, which is the multi-release jar pattern: one artefact whose behaviour changes depending on the Java version running it. There is an equalsverifier-core module, an equalsverifier-aggregator module that ties the published artefacts together, and three release modules named for the variant they produce, equalsverifier-release-main, equalsverifier-release-nodep and equalsverifier-release-verify.
The test modules are the other interesting group, because each one pins a scenario. There is a module for the module system, for JPMS, one for Kotlin, one for Kotlin without reflection, one for Mockito, one for a signed jar, one for the library's own tests and one for test helpers. That list is a set of environments where an equality checker can break in ways that a normal test does not, a signed jar whose classes are sealed, a Kotlin data class whose equals is generated, a module system that restricts access to internals.
The verification release module is the piece that ties the packaging together, and the justfile runs it as part of the full verify target.
There is also an arcmutate-licence.txt in the root, which is the licence of the mutation testing tool the project uses, and a jreleaser configuration for the release automation.
The build runs mutation testing, formatting and release checks
The justfile is short and it describes the whole verification story.
There is a clean target that runs mvn clean with the release profile, a compile target that runs mvn test-compile, a test target that runs mvn test, and a verify target that runs mvn verify with the release profile, which by the comment includes the release verification tests and the static analysis. Formatting is enforced by mvn spotless:apply, and there is a local-install target that installs the library into the local Maven cache with the tests and the static analysis skipped, which is the target a contributor uses to try the change in another project.
The one that catches the eye is the last. The pitest target runs mvn clean test with the Pitest mutation coverage plugin, and mutation testing is the right tool for a library like this one. The code in question is a set of checks, and a check that is not exercised is a check that does not work. Mutation testing measures that directly, by breaking the code on purpose and confirming that the tests notice.
For a library whose entire value is the errors it reports, the quality of its own test suite is not a secondary concern, and running mutation coverage as part of the routine is a proportionate response to that.
The repository also carries a build directory, a docs directory, a pom.xml at the root, the changelog, the contribution guidelines and the code of conduct.
An issue before a pull request, and a steady release line
The contribution section is short but it sets a clear condition.
Pull requests are welcome, and if you plan to open one you are asked to register an issue or start a discussion first, so the work can be discussed before it is written. The author's stated reason is that it would be a shame to put a lot of work into something that is not a good fit for the project, and he adds that he can help by pointing out where certain things live in the codebase.
That is the policy of a maintainer with a long-lived project and a specific idea of what belongs in it, and it is best respected as a user too, because the alternative, a pull request that adds a configuration flag for a case the maintainer does not think exists, costs both sides.
The release history shows the project is kept up to date. Version 4.5 was published on 2026-04-17, 4.5.1 on 2026-08-21 and 4.5.2 on 2026-08-27, and the last push to the repository was on 2026-10-16. So the release line moves in small increments, and the maintenance is recent enough that the version in your build file is not stale.
For a library this small, that combination of a strict opinionated checker, a mutation tested test suite and an issue-first contribution process explains why the project has lasted from 2009 to now without changing its shape.
The one thing left to check before you adopt it is your own equals implementations, because the library will find the ones that are wrong, and that is a good thing, but it is not free.
Editorial conclusion
EqualsVerifier fits a Java team that has been bitten by an equals implementation that breaks the contract, a mutable field in the hash, an inheritance asymmetry, because a single line in a test now covers the whole set of rules the language defines. It is the wrong choice if you want a quiet test that only checks the obvious cases, since the library is strict by design and its simple mode is a deliberate relaxation rather than a second opinion. It is also the wrong choice if you are on an old Java version the project no longer ships, since the release modules are split by Java version. Before you adopt it, add the dependency with test scope so it never reaches production, read the errors it throws rather than switching to the lenient mode, and check which Java version your build targets against the modules the repository publishes.
Frequently asked questions
What does EqualsVerifier do?
It is an Apache 2.0 licensed Java library for unit tests that verifies whether a class meets the contract for its equals and hashCode methods. You add it with test scope and call EqualsVerifier.forClass(Foo.class).verify() in a test.
What if EqualsVerifier is too strict?
You can use the lenient entry point, EqualsVerifier.simple().forClass(Foo.class).verify(), which throws fewer errors. The readme advises fixing the reported errors instead, since the library throws them for a reason.
How do I add EqualsVerifier to a Maven project?
With the dependency nl.jqno.equalsverifier:equalsverifier at version 4.5.2 and a scope of test. There is also a fat jar with no transitive dependencies, published under the artifact id equalsverifier-nodep.
How is the library itself tested?
The repository has separate test modules for the module system, for Kotlin with and without reflection, for Mockito, for signed jars and for test helpers, and the justfile runs mutation coverage with Pitest, so the checks the library performs are themselves measured for whether the tests would notice them breaking.
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/jqno-equalsverifier)Was this useful?