How would you make JUnit Jupiter's @TempDir hand out directories from somewhere other than the default operating-system temp location — for example an in-memory filesystem or a fixed parent directory?
answer
- TempDirFactory.createTempDirectory + close()
- @TempDir(factory = X.class)
- junit.jupiter.tempdir.factory.default
- Jimfs = in-memory, Path only
- File cannot leave the default filesystem
basics
~20 sImplement JUnit's TempDirFactory interface, whose createTempDirectory method returns the Path to use, and point at it with @TempDir(factory = MyFactory.class), or set it for the whole suite with the junit.jupiter.tempdir.factory.default configuration parameter. The injected field must be a Path, not a File.
solid answer
~40 sJupiter lets you replace the directory-creation strategy with a `TempDirFactory`. Implement `createTempDirectory(AnnotatedElementContext, ExtensionContext)` to return the `Path` for that declaration, and optionally `close()` to release factory-owned resources after Jupiter has deleted the directory. Select it per declaration: ```java @TempDir(factory = JimfsTempDirFactory.class) Path dir; ``` or globally with the configuration parameter `junit.jupiter.tempdir.factory.default=com.example.JimfsTempDirFactory`. Typical motivations: an **in-memory filesystem** (Jimfs) for speed and to guarantee tests never touch real disk; a **fixed parent directory** on a volume with space or the right permissions when `java.io.tmpdir` is unsuitable on CI agents; a filesystem configured for a specific OS flavour so path-separator and case-sensitivity behaviour is deterministic. The key constraint: a non-default filesystem only works when you inject a `Path`. `java.io.File` can only represent the default filesystem, so a factory returning a Jimfs path fails for `File` declarations.
code
java · 21 linesimport org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.io.TempDirFactory;
import org.junit.jupiter.api.extension.AnnotatedElementContext;
import java.io.IOException;
import java.nio.file.*;
class BuildDirTempDirFactory implements TempDirFactory {
@Override
public Path createTempDirectory(AnnotatedElementContext element,
ExtensionContext context) throws IOException {
Path parent = Files.createDirectories(Path.of("build", "test-temp"));
String name = context.getRequiredTestClass().getSimpleName();
return Files.createTempDirectory(parent, name + "-");
}
}
class ExportTest {
@TempDir(factory = BuildDirTempDirFactory.class)
Path dir;
}go deeper
It is enough to know the default location can be customised and that this is not everyday material.
Name TempDirFactory, the factory attribute and the global configuration parameter, and the Path-not-File constraint.
Give concrete motivations — in-memory speed and hermeticity, deterministic filesystem semantics, awkward CI temp volumes — implement the interface confidently, and flag that production code must be Path-based.
Treat it as a suite-wide policy lever: standardising temp-directory placement across agents or moving I/O-heavy suites in-memory, weighed against the coupling it imposes on production code's filesystem abstraction.
## Why replace the default The default strategy — `Files.createTempDirectory` under `java.io.tmpdir` — is right most of the time, but there are real reasons to override it. - **Speed and hermeticity.** An in-memory filesystem such as Google's Jimfs removes disk I/O entirely and guarantees a test cannot leave anything behind, even if cleanup fails. - **Deterministic filesystem semantics.** Jimfs can be configured to emulate Unix, Windows or macOS conventions, so tests of path handling, case sensitivity or separators behave the same on every developer machine. - **Environment constraints.** Some CI agents have a tiny or noexec `/tmp`, or a temp path so long that Windows path-length limits bite. Directing temp directories to a known volume solves it centrally instead of per test. - **Observability.** A factory can create directories under a build-output folder so a CI job can archive them as artefacts. ## The interface `org.junit.jupiter.api.io.TempDirFactory` has one method to implement and one optional: ```java Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception; default void close() throws IOException { } ``` `createTempDirectory` is called once per `@TempDir` declaration and returns the directory to inject; the implementation is responsible for actually creating it. The two context arguments let you vary behaviour: `elementContext` exposes the annotated field or parameter (so you can read your own annotations from it), and `extensionContext` exposes the test or class being executed (so you can name directories after the test, which is a genuinely useful trick for archived artefacts). `close()` is invoked after Jupiter has performed its own cleanup of the directory. That is where an in-memory-filesystem factory closes the `FileSystem` instance it created. Jupiter instantiates a new factory instance per declaration, so per-instance state is safe. ## A Jimfs factory ```java class JimfsTempDirFactory implements TempDirFactory { private FileSystem fileSystem; @Override public Path createTempDirectory(AnnotatedElementContext e, ExtensionContext c) throws IOException { fileSystem = Jimfs.newFileSystem(Configuration.unix()); return Files.createTempDirectory(fileSystem.getPath("/"), "junit"); } @Override public void close() throws IOException { fileSystem.close(); } } ``` ## Selecting the factory Per declaration: `@TempDir(factory = JimfsTempDirFactory.class)`. Suite-wide: the configuration parameter ``` junit.jupiter.tempdir.factory.default = com.example.JimfsTempDirFactory ``` read from `junit-platform.properties` on the test classpath, a system property, or launcher-supplied parameters. An explicit `factory` attribute overrides the global default. The sentinel `TempDirFactory.Standard` represents the built-in behaviour. ## Constraints and gotchas 1. **`Path` only for non-default filesystems.** `java.io.File` is hardwired to the default filesystem; a factory returning a Jimfs path cannot be injected into a `File`, and Jupiter fails the declaration. Since a factory is usually adopted precisely to get off the real disk, standardise on `Path`. 2. **Your production code must be filesystem-agnostic.** Code that calls `new File(...)`, `FileInputStream`, or `Paths.get(...)` internally will ignore the in-memory filesystem. Only code written against `Path`, `Files` and the `Path` handed to it works. In practice this is a design constraint the factory imposes on the code under test — often a good one, sometimes a dealbreaker. 3. **Cleanup still applies.** Jupiter deletes the returned directory according to the `CleanupMode` as usual, then calls `close()`. A factory does not opt out of cleanup. 4. **Third-party dependency.** Jimfs is not part of JUnit; it is a separate test-scoped dependency. ## When not to bother For ordinary tests the default is fine and adding a factory is complexity with no payoff. The honest interview answer names the mechanism, gives one or two real motivations, and is clear that it is a specialist tool — reached for when disk is a genuine problem (speed, hermeticity, agent constraints) or when filesystem semantics must be pinned, not by default.
- What breaks if you point @TempDir at an in-memory filesystem but the code under test uses java.io.File internally?It silently escapes the in-memory filesystem or fails outright. java.io.File and FileInputStream/FileOutputStream can only address the default filesystem, so code built on them cannot see a Jimfs path; converting with path.toFile() throws UnsupportedOperationException for a non-default filesystem. Using a factory therefore requires the production code to be written against Path and Files, which is a real constraint worth checking before adopting one.
- When is TempDirFactory.close() called relative to the directory's deletion?After Jupiter has finished its own cleanup of the temp directory for that declaration, honouring the configured CleanupMode. That ordering lets a factory that created a FileSystem instance close it once nothing needs the directory any more. A new factory instance is created per declaration, so holding per-instance state such as the FileSystem is safe.
saying these in an interview costs you the question
- Claiming you must write a whole custom extension because @TempDir is not customisable
- Injecting into java.io.File while expecting an in-memory filesystem to be used
- Assuming a factory disables cleanup — CleanupMode still applies
- Thinking the factory is chosen only globally, or only per annotation, when both are supported
- Reaching for a custom factory by default rather than for a specific speed, hermeticity or environment reason