junit-team/junit5 · error · PreconditionViolationException

temp directory must be a directory

Error message

temp directory must be a directory

What it means

Thrown as PreconditionViolationException inside the CloseablePath constructor when factory.createTempDirectory returned null or returned a Path that is not an existing directory (Files.isDirectory is false). The constructor closes the (already half-built) CloseablePath first to avoid leaking resources, then throws.

Solutions

  1. Fix the custom TempDirFactory so createTempDirectory always returns a directory that exists (use Files.createDirectories(...) before returning).
  2. Have the factory throw an exception rather than returning null when it cannot create the dir.
  3. Switch to the default factory if you do not need custom placement.

Example fix

// before
public Path createTempDirectory(AnnotatedElementContext ctx, ExtensionContext ec) {
    return Path.of("/tmp/maybe-not-created"); // not actually a dir
}
// after
public Path createTempDirectory(AnnotatedElementContext ctx, ExtensionContext ec) throws IOException {
    Path p = Path.of("/tmp/mybase").resolve("junit-" + UUID.randomUUID());
    Files.createDirectories(p);
    return p;
}
Defensive patterns

Strategy: validation

Validate before calling

Path candidate = factory.createTempDirectory(ctx, ec);
if (candidate == null || !Files.isDirectory(candidate)) {
    throw new IllegalStateException("Factory returned non-directory: " + candidate);
}

Prevention

When it happens

Trigger: A custom TempDirFactory.createTempDirectory returns null or returns a path that does not exist / is a regular file. After this.dir is assigned, the precondition `this.dir == null || !Files.isDirectory(this.dir)` triggers close() then throws.

Common situations: Custom factory returns Paths.get("/some/path") without actually creating it; factory returns null on failure instead of throwing; factory returns a path that was concurrently deleted; misbehaving factory in a library.

Related errors


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

Appendix: source

Thrown at junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java:273

	static class CloseablePath implements Store.CloseableResource, AutoCloseable {

		private final @Nullable Path dir;
		private final TempDirFactory factory;
		private final Cleanup cleanup;
		private final AnnotatedElementContext elementContext;
		private final ExtensionContext extensionContext;

		private CloseablePath(TempDirFactory factory, Cleanup cleanup, Class<?> elementType,
				AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception {
			this.dir = factory.createTempDirectory(elementContext, extensionContext);
			this.factory = factory;
			this.cleanup = cleanup;
			this.elementContext = elementContext;
			this.extensionContext = extensionContext;

			if (this.dir == null || !Files.isDirectory(this.dir)) {
				close();
				throw new PreconditionViolationException("temp directory must be a directory");
			}

			if (elementType == File.class && !this.dir.getFileSystem().equals(FileSystems.getDefault())) {
				close();
				throw new PreconditionViolationException(
					"temp directory with non-default file system cannot be injected into " + File.class.getName()
							+ " target");
			}
		}

		Path get() {
			return requireNonNull(this.dir);
		}

		@Override
		public void close() throws IOException {
			try {
				if (this.dir != null) {

View on GitHub (pinned to f070c699a0)