How to Use JUnit’s ErrorCollector Rule
Use JUnit 4's ErrorCollector to run independent checks after failures and report collected problems together, with examples for matchers, exceptions, and callables.
ErrorCollector is a JUnit 4 rule that lets a test continue after a check fails, then reports the collected failures together when the rule verifies the test. Declare it as a field annotated with @Rule, and use its methods for checks that are independent of one another.
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
import static org.hamcrest.CoreMatchers.is;
public class RowValidationTest {
@Rule
public ErrorCollector collector = new ErrorCollector();
@Test
public void checksSeveralRows() {
collector.checkThat("first row", actualFirst, is(expectedFirst));
collector.checkThat("second row", actualSecond, is(expectedSecond));
}
}
If the first matcher fails, the second call still runs. JUnit reports the collected problems at the end of the test through the rule’s verification step. ErrorCollector has been available since JUnit 4.7. See the JUnit API documentation and the JUnit 4.13 implementation.
1. Add the rule to a JUnit 4 test
JUnit 4’s ErrorCollector lives in org.junit.rules. Add JUnit 4 to the test dependencies in your project, then declare a public rule field on the test class. With Maven, a typical JUnit 4 test dependency looks like this:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
Use the JUnit version selected by your project if it is already managed by a parent POM or build configuration. The rule is a JUnit 4 API; do not assume a JUnit 5 test can use it directly without a JUnit 4 compatibility setup.
2. Choose the right collection method
The rule offers three common ways to record a failure. Choose based on whether you have a matcher check, an existing throwable, or a piece of code that may throw.
| Method | Use it for | Behavior |
|---|---|---|
checkThat(value, matcher) |
A Hamcrest matcher assertion | Records a matcher failure and allows later test statements to continue. |
checkThat(reason, value, matcher) |
A matcher assertion that needs context | Includes a reason such as the row or field name in the failure. |
addError(Throwable) |
An exception or error you already have | Adds that throwable to the collected failures. |
checkSucceeds(Callable<T>) |
An operation that should return a value but may throw | Returns the value on success; records a thrown throwable and returns null on failure. |
Use reason strings to identify failed checks
When checking a collection or table, include enough context to tell which item failed. Without a reason, the matcher still identifies the expected and actual values, but a row or field label can make a batch report much easier to act on.
collector.checkThat("customer 104 email", customer104.getEmail(), is("a@example.test"));
collector.checkThat("customer 105 email", customer105.getEmail(), is("b@example.test"));
Record an existing error with addError
Use addError when an earlier operation produced a throwable that you want reported with the other collected failures. The call records it; it does not throw it immediately.
collector.addError(new IllegalStateException("first setup issue"));
collector.addError(new AssertionError("second validation issue"));
In production tests, pass the actual throwable when you have one so the report retains the exception type and message. Do not catch an exception and silently discard it.
Wrap throwable operations with checkSucceeds
checkSucceeds runs a Callable<T>. If the callable completes, its result is returned. If it throws, the throwable is collected and the method returns null. Account for that null result before using the returned value.
import java.util.concurrent.Callable;
@Test
public void parsesIndependentInputs() {
String first = collector.checkSucceeds(new Callable<String>() {
@Override
public String call() throws Exception {
return parse("valid input");
}
});
String second = collector.checkSucceeds(new Callable<String>() {
@Override
public String call() throws Exception {
return parse("another input");
}
});
if (first != null) {
collector.checkThat("first parsed value", first, is("expected one"));
}
if (second != null) {
collector.checkThat("second parsed value", second, is("expected two"));
}
}
The null check matters: a null may be a legitimate successful result too, so if your callable is allowed to return null, track success separately or structure the test so it does not confuse a legitimate null with the failure return.
3. Write checks that can safely continue
ErrorCollector is most useful when checks are independent: a mismatch in one row should not prevent checking the next row. Avoid collecting a failure and then blindly using invalid state as though the earlier assertion passed. For example, if a lookup can fail and a later check requires the lookup result, guard the dependent operation or keep that sequence as a regular fail-fast assertion.
@Test
public void validatesRows() {
for (int i = 0; i < rows.size(); i++) {
Row row = rows.get(i);
collector.checkThat("row " + i + " has an id", row.getId(), is(expectedIds.get(i)));
collector.checkThat("row " + i + " is enabled", row.isEnabled(), is(true));
}
}
The collector gathers failures made through its methods, or throwables explicitly passed to addError. Do not assume an arbitrary exception from elsewhere in the test body will be intercepted. If an operation may throw and you want it collected, wrap it with checkSucceeds or catch the throwable deliberately and pass it to addError.
4. Understand when the test fails
In JUnit 4.13, ErrorCollector extends Verifier and stores collected throwables. During verification, it asks MultipleFailureException.assertEmpty(errors) to fail if the list is not empty. That is why execution can continue through subsequent collector calls before the test is ultimately marked failed.
This does not mean every operation is safe to continue after a mismatch. A null dereference, array bounds error, or unrelated exception thrown directly in the test can stop the method before later checks execute. Use the collector around the checks or operations whose failures you want aggregated.
5. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Compilation says ErrorCollector cannot be resolved |
JUnit 4 is not on the test compile classpath, or the import is missing. | Add the project’s JUnit 4 test dependency and import org.junit.rules.ErrorCollector. |
| The test stops at the first failure | The assertion was made directly with a fail-fast assertion instead of a collector method. | Use collector.checkThat, wrap a throwable operation in checkSucceeds, or add an existing throwable with addError. |
| A later line throws a null-related exception | checkSucceeds returned null because its callable threw, and the test used that result. |
Guard use of the returned value and report dependent checks only when the prerequisite succeeded. |
| The report does not include an exception from the test body | The exception was thrown outside a collector method and was not explicitly added. | Wrap the operation in checkSucceeds or catch the throwable and call addError. |
| The test fails even though later checks ran | This is the expected result when at least one problem was collected. | Read the combined failure report, fix each identified condition, and rerun. |
| A rule declaration is ignored by the test environment | The test may be using a runner or framework integration with different rule support. | Confirm the test is running as a JUnit 4 test and check that runner’s documented rule behavior. |
6. Performance, reliability, and cost
ErrorCollector adds little work beyond recording failures and assembling their report, but the expensive part is usually the checks themselves: database queries, network requests, parsing, or browser work. Keep collected checks independent and avoid repeating costly setup inside each check. Where setup is shared, compute it once and collect validations of the resulting data.
For reliable diagnostics, use stable labels and keep each collected assertion narrow. A collector does not make a flaky dependency reliable, retry an operation, or isolate state between checks. It changes how failures from wrapped checks are accumulated and reported. JUnit itself is a test dependency; using this rule does not require a paid service.
7. Or skip the browser setup
If a test workflow also needs website captures, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns an image or PDF; the code below saves a PNG response from a target URL. See the ScreenshotNeo API docs for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.png
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.png", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.png', Buffer.from(await res.arrayBuffer()));
- Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; each removal step can be turned off.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Response headers report the page verdict and billing status.
- An MCP server lets Claude, Cursor, and other MCP clients use screenshot, page information, and PDF capture tools.
- The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
8. FAQ
Does ErrorCollector prevent a test from being marked failed?
No. It lets the test continue through collected checks, then fails verification if it recorded one or more problems.
Can I use it with JUnit 5?
It is a JUnit 4 rule. JUnit 5 uses a different extension model; this dossier does not establish compatibility details for every mixed-engine setup.
Should every assertion in a test use ErrorCollector?
No. Use it where seeing multiple independent failures in one run is useful. Keep dependent steps fail-fast or guard them when prerequisites fail.


