junit-team/junit5 · error · PreconditionViolationException

Filter [ ] must implement , , or .

Error message

Filter [%s] must implement %s, %s, or %s.

What it means

LauncherDiscoveryRequestBuilder.storeFilter rejects any Filter that is not an EngineFilter, a PostDiscoveryFilter, or a DiscoveryFilter. The builder sorts filters into the correct bucket; a filter implementing none of those three interfaces is a programming error since the launcher cannot apply it.

Solutions

  1. Make your custom filter implement org.junit.platform.launcher.PostDiscoveryFilter (for after-discovery filtering) or org.junit.platform.launcher.DiscoveryFilter (for during-discovery filtering), or use org.junit.platform.launcher.EngineFilter for engine selection.
  2. Use the built-in factory methods (includeEngines, excludeEngines, includeClassNamePatterns) instead of hand-rolling a Filter.
  3. If filtering by class/method, use the provided static helpers which return the correct subtype.

Example fix

// before
public class MyFilter implements Filter<TestDescriptor> { ... } // no marker -> throws
request().filters(new MyFilter());
// after
public class MyFilter implements PostDiscoveryFilter { ... }
request().filters(new MyFilter());
Defensive patterns

Strategy: type-guard

Validate before calling

// Ensure the filter implements one of the three supported interfaces
Filter<?> f = myFilter;
boolean ok = f instanceof EngineFilter || f instanceof PostDiscoveryFilter || f instanceof DiscoveryFilter;
if (!ok) throw new IllegalArgumentException("filter type not supported: " + f.getClass());

Type guard

static boolean isSupportedFilter(Filter<?> f) {
    return f instanceof EngineFilter || f instanceof PostDiscoveryFilter || f instanceof DiscoveryFilter;
}

Try / catch

try {
    builder.filters(myFilter).build();
} catch (PreconditionViolationException e) {
    // message names which interface to implement - update the filter class
}

Prevention

When it happens

Trigger: Calling LauncherDiscoveryRequestBuilder.filters(filter) with a Filter whose runtime type implements only the base Filter interface (or some unrelated subinterface). For example, passing a raw Filter lambda, a org.junit.platform.engine.Filter where the generic bound is wrong, or a custom filter that forgot to extend the right marker interface.

Common situations: Authoring a custom filter and forgetting to implement DiscoveryFilter / PostDiscoveryFilter; passing a ClassFilter or ClassNameFilter of the wrong subtype; refactoring that widened/narrowed the filter interface; accidental raw-type usage suppressing the generics check.

Related errors


AI-assisted analysis of junit-team/junit5@f070c699a0 (2026-08-11). Data as JSON: /api/errors/aae64ee5e85187f6. Report an issue: GitHub.

Appendix: source

Thrown at junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java:369

	@API(status = MAINTAINED, since = "1.14")
	public LauncherDiscoveryRequestBuilder outputDirectoryCreator(OutputDirectoryCreator outputDirectoryCreator) {
		this.outputDirectoryCreator = Preconditions.notNull(outputDirectoryCreator,
			"outputDirectoryCreator must not be null");
		return this;
	}

	private void storeFilter(Filter<?> filter) {
		if (filter instanceof EngineFilter engineFilter) {
			this.engineFilters.add(engineFilter);
		}
		else if (filter instanceof PostDiscoveryFilter postDiscoveryFilter) {
			this.postDiscoveryFilters.add(postDiscoveryFilter);
		}
		else if (filter instanceof DiscoveryFilter<?> discoveryFilter) {
			this.discoveryFilters.add(discoveryFilter);
		}
		else {
			throw new PreconditionViolationException(
				"Filter [%s] must implement %s, %s, or %s.".formatted(filter, EngineFilter.class.getSimpleName(),
					PostDiscoveryFilter.class.getSimpleName(), DiscoveryFilter.class.getSimpleName()));
		}
	}

	/**
	 * Builds this discovery request and returns a new builder for creating a
	 * {@link org.junit.platform.launcher.LauncherExecutionRequest} that is
	 * initialized to contain the resulting discovery request.
	 *
	 * @return a new {@link LauncherExecutionRequestBuilder}
	 * @since 6.0
	 */
	@API(status = EXPERIMENTAL, since = "6.0")
	public LauncherExecutionRequestBuilder forExecution() {
		return LauncherExecutionRequestBuilder.request(build());
	}

View on GitHub (pinned to f070c699a0)