ChatGPT解决这个技术问题 Extra ChatGPT

How to create a temporary directory/folder in Java?

Is there a standard and reliable way of creating a temporary directory inside a Java application? There's an entry in Java's issue database, which has a bit of code in the comments, but I wonder if there is a standard solution to be found in one of the usual libraries (Apache Commons etc.) ?


G
GreenGiant

If you are using JDK 7 use the new Files.createTempDirectory class to create the temporary directory.

Path tempDirWithPrefix = Files.createTempDirectory(prefix);

Before JDK 7 this should do it:

public static File createTempDirectory()
    throws IOException
{
    final File temp;

    temp = File.createTempFile("temp", Long.toString(System.nanoTime()));

    if(!(temp.delete()))
    {
        throw new IOException("Could not delete temp file: " + temp.getAbsolutePath());
    }

    if(!(temp.mkdir()))
    {
        throw new IOException("Could not create temp directory: " + temp.getAbsolutePath());
    }

    return (temp);
}

You could make better exceptions (subclass IOException) if you want.


This is dangerous. Java is known not to delete files immediately, so mkdir may fail sometimes
@Demiurg The only case of a file not being deleted immediately is on Windows when the file is already open (it could be open by a virus scanner for example). Do you have other documentation to show otherwise (I am curious about things like that :-)? If it happens regularly then the above code won't work, if it is rare then lopping the call to the above code until the delete happens (or some max tries is reached) would work.
@Demiurg Java is known not to delete files immediately. That's true, even if you don't open it. So, a more safe way is temp.delete(); temp = new File(temp.getPath + ".d"); temp.mkdir(); ..., temp.delete();.
This code has a race condition between delete() and mkdir(): A malicious process could create the target directory in the meantime (taking the name of the recently-created file). See Files.createTempDir() for an alternative.
I like the ! to stand out, too easy to miss it. I read a lot of code written by students... if(!i) is common enough to be annoying :-)
J
Jared Rummler

The Google Guava library has a ton of helpful utilities. One of note here is the Files class. It has a bunch of useful methods including:

File myTempDir = Files.createTempDir();

This does exactly what you asked for in one line. If you read the documentation here you'll see that the proposed adaptation of File.createTempFile("install", "dir") typically introduces security vulnerabilities.


I wonder what vulnerability you refer to. This approach does not appear to create a race condition as File.mkdir() is suppposed to fail if such the directory already exists (created by an attacker). I don't think this call will follow through a malicious symlinks either. Could you clarify what you have meant?
@abb: I don't know the details of the race condition that is mentioned in the Guava documentation. I suspect that the documentation is correct given that it specifically calls out the problem.
@abb You're right. As long as the return of mkdir() is checked then it would be secure. The code Spina points to uses this mkdir() method. grepcode.com/file/repo1.maven.org/maven2/com.google.guava/guava/… . This is only a potential issue on Unix systems when using the /tmp directory because it has the sticky bit enabled.
@SarelBotha thanks for filling in the blank here. I'd been wondering idly about this for quite some time.
m
matsev

If you need a temporary directory for testing and you are using jUnit, @Rule together with TemporaryFolder solves your problem:

@Rule
public TemporaryFolder folder = new TemporaryFolder();

From the documentation:

The TemporaryFolder Rule allows creation of files and folders that are guaranteed to be deleted when the test method finishes (whether it passes or fails)

Update:

If you are using JUnit Jupiter (version 5.1.1 or greater), you have the option to use JUnit Pioneer which is the JUnit 5 Extension Pack.

Copied from the project documentation:

For example, the following test registers the extension for a single test method, creates and writes a file to the temporary directory and checks its content.

@Test
@ExtendWith(TempDirectory.class)
void test(@TempDir Path tempDir) {
    Path file = tempDir.resolve("test.txt");
    writeFile(file);
    assertExpectedFileContent(file);
}

More info in the JavaDoc and the JavaDoc of TempDirectory

Gradle:

dependencies {
    testImplementation 'org.junit-pioneer:junit-pioneer:0.1.2'
}

Maven:

<dependency>
   <groupId>org.junit-pioneer</groupId>
   <artifactId>junit-pioneer</artifactId>
   <version>0.1.2</version>
   <scope>test</scope>
</dependency>

Update 2:

The @TempDir annotation was added to the JUnit Jupiter 5.4.0 release as an experimental feature. Example copied from the JUnit 5 User Guide:

@Test
void writeItemsToFile(@TempDir Path tempDir) throws IOException {
    Path file = tempDir.resolve("test.txt");

    new ListWriter(file).write("a", "b", "c");

    assertEquals(singletonList("a,b,c"), Files.readAllLines(file));
}

Doesn't work in JUnit 4.8.2 on Windows 7! (Permissions issue)
@CraigRinger: Why is it unwise to rely on this?
@AdamParkin Honestly, I don't remember anymore. Explanation fail!
Doesn't work in 4.11. getRoot() and newFile() both complained "temp dir not created yet". Besides, "Folder" is ugly Windows term. It's a directory.
The main benefit of this approach is that the directory is managed by JUnit (created before the test and deleted recursively after the test). And it does work. If you get "temp dir not created yet", it might be because you forgot @Rule or the field in not public.
G
Greg Price

Naively written code to solve this problem suffers from race conditions, including several of the answers here. Historically you could think carefully about race conditions and write it yourself, or you could use a third-party library like Google's Guava (as Spina's answer suggested.) Or you could write buggy code.

But as of JDK 7, there is good news! The Java standard library itself now provides a properly working (non-racy) solution to this problem. You want java.nio.file.Files#createTempDirectory(). From the documentation:

public static Path createTempDirectory(Path dir,
                       String prefix,
                       FileAttribute<?>... attrs)
                                throws IOException

Creates a new directory in the specified directory, using the given prefix to generate its name. The resulting Path is associated with the same FileSystem as the given directory. The details as to how the name of the directory is constructed is implementation dependent and therefore not specified. Where possible the prefix is used to construct candidate names.

This effectively resolves the embarrassingly ancient bug report in the Sun bug tracker which asked for just such a function.


A
Andres Kievsky

This is the source code to the Guava library's Files.createTempDir(). It's nowhere as complex as you might think:

public static File createTempDir() {
  File baseDir = new File(System.getProperty("java.io.tmpdir"));
  String baseName = System.currentTimeMillis() + "-";

  for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
    File tempDir = new File(baseDir, baseName + counter);
    if (tempDir.mkdir()) {
      return tempDir;
    }
  }
  throw new IllegalStateException("Failed to create directory within "
      + TEMP_DIR_ATTEMPTS + " attempts (tried "
      + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')');
}

By default:

private static final int TEMP_DIR_ATTEMPTS = 10000;

See here


J
Joachim Sauer

Do not use deleteOnExit() even if you explicitly delete it later.

Google 'deleteonexit is evil' for more info, but the gist of the problem is:

deleteOnExit() only deletes for normal JVM shutdowns, not crashes or killing the JVM process. deleteOnExit() only deletes on JVM shutdown - not good for long running server processes because: The most evil of all - deleteOnExit() consumes memory for each temp file entry. If your process is running for months, or creates a lot of temp files in a short time, you consume memory and never release it until the JVM shuts down.


We have a JVM where class and jar files get hidden files underneath created by the JVM, and this extra information takes quite a while to delete. When doing hot redeploys on web containers exploding WARs, the JVM can literaly take minutes to clean up after finishing but before exiting when having run for a few hours.
s
seeker

As of Java 1.7 createTempDirectory(prefix, attrs) and createTempDirectory(dir, prefix, attrs) are included in java.nio.file.Files

Example: File tempDir = Files.createTempDirectory("foobar").toFile();


K
Keith

This is what I decided to do for my own code:

/**
 * Create a new temporary directory. Use something like
 * {@link #recursiveDelete(File)} to clean this directory up since it isn't
 * deleted automatically
 * @return  the new directory
 * @throws IOException if there is an error creating the temporary directory
 */
public static File createTempDir() throws IOException
{
    final File sysTempDir = new File(System.getProperty("java.io.tmpdir"));
    File newTempDir;
    final int maxAttempts = 9;
    int attemptCount = 0;
    do
    {
        attemptCount++;
        if(attemptCount > maxAttempts)
        {
            throw new IOException(
                    "The highly improbable has occurred! Failed to " +
                    "create a unique temporary directory after " +
                    maxAttempts + " attempts.");
        }
        String dirName = UUID.randomUUID().toString();
        newTempDir = new File(sysTempDir, dirName);
    } while(newTempDir.exists());

    if(newTempDir.mkdirs())
    {
        return newTempDir;
    }
    else
    {
        throw new IOException(
                "Failed to create temp dir named " +
                newTempDir.getAbsolutePath());
    }
}

/**
 * Recursively delete file or directory
 * @param fileOrDir
 *          the file or dir to delete
 * @return
 *          true iff all files are successfully deleted
 */
public static boolean recursiveDelete(File fileOrDir)
{
    if(fileOrDir.isDirectory())
    {
        // recursively delete contents
        for(File innerFile: fileOrDir.listFiles())
        {
            if(!FileUtilities.recursiveDelete(innerFile))
            {
                return false;
            }
        }
    }

    return fileOrDir.delete();
}

This is insecure. See comment by Joachim Sauer in the first (equally insecure) option. Proper way to check for existence of a file or dir and than to grab the filename, atomically, is by creating the file or dir.
@zbyszek javadocs say "The UUID is generated using a cryptographically strong pseudo random number generator." Given that how does a malicious process create a dir with the same name between exists() and mkdirs(). In fact looking at this now I think my exists() test might be a little silly.
Keith: UUID being secure or not isn't crucial in this case. It is enough for the information about the name you queried to somehow "leak". For example, let's say that the file being created is on an NFS filesystem, and the attacker can listen (passively) to packets. Or the random generator state has been leaked. In my comment I said that your solution is equally insecure as the accepted answer, but this isn't fair: the accepted one is trivial to defeat with inotify, and this one is much harder to defeat. Nevertheless, in some scenarios it is certainly possible.
I had the same thought and implemented a solution using random UUIDs bit like this one. No check for exists, just one attempt to create - strong RNG used by randomUUID method pretty much guarantees no collisions (can be used for generating primary keys in DB tables, done this myself and never known a collision), so pretty confident. If anyone's not sure, check out stackoverflow.com/questions/2513573/…
If you look at Java's implementation, they just generate random names until there is no collision. Their max attempts are infinite. So if somebody malicious were to keep guessing your file/directory name, it would loop forever. Here is a link to the source: hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/9fb81d7a2f16/src/share/… I was thinking that it could somehow lock the filesystem so it could atomically generate a unique name and create the directory, but I guess it doesn't do that according to the source code.
P
Paul Tomblin

Well, "createTempFile" actually creates the file. So why not just delete it first, and then do the mkdir on it?


You should always check the return value for mkdir(). If that is false then it means the directory already existed. This can cause security problems, so consider whether this should raise an error in your application or not.
See the note about the race condition in the other answer.
This I like, barring the race
m
matt b

This code should work reasonably well:

public static File createTempDir() {
    final String baseTempPath = System.getProperty("java.io.tmpdir");

    Random rand = new Random();
    int randomInt = 1 + rand.nextInt();

    File tempDir = new File(baseTempPath + File.separator + "tempDir" + randomInt);
    if (tempDir.exists() == false) {
        tempDir.mkdir();
    }

    tempDir.deleteOnExit();

    return tempDir;
}

What if the directory already exists and you don't have read/write access to it or what if it's a regular file? You also have a race condition there.
Also, deleteOnExit will not delete non-empty directories.
M
Michael Myers

As discussed in this RFE and its comments, you could call tempDir.delete() first. Or you could use System.getProperty("java.io.tmpdir") and create a directory there. Either way, you should remember to call tempDir.deleteOnExit(), or the file won't be deleted after you're done.


Isn't this property called "java.io.tmpdir", not "...temp"? See java.sun.com/j2se/1.4.2/docs/api/java/io/File.html
Quite so. I should have verified before repeating what I read.
The java.io.tmpdir is shared so you need to do all the usual voodoo to avoid stepping on somebody elses toes.
A
Arne

Just for completion, this is the code from google guava library. It is not my code, but I think it is valueable to show it here in this thread.

  /** Maximum loop count when creating temp directories. */
  private static final int TEMP_DIR_ATTEMPTS = 10000;

  /**
   * Atomically creates a new directory somewhere beneath the system's temporary directory (as
   * defined by the {@code java.io.tmpdir} system property), and returns its name.
   *
   * <p>Use this method instead of {@link File#createTempFile(String, String)} when you wish to
   * create a directory, not a regular file. A common pitfall is to call {@code createTempFile},
   * delete the file and create a directory in its place, but this leads a race condition which can
   * be exploited to create security vulnerabilities, especially when executable files are to be
   * written into the directory.
   *
   * <p>This method assumes that the temporary volume is writable, has free inodes and free blocks,
   * and that it will not be called thousands of times per second.
   *
   * @return the newly-created directory
   * @throws IllegalStateException if the directory could not be created
   */
  public static File createTempDir() {
    File baseDir = new File(System.getProperty("java.io.tmpdir"));
    String baseName = System.currentTimeMillis() + "-";

    for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
      File tempDir = new File(baseDir, baseName + counter);
      if (tempDir.mkdir()) {
        return tempDir;
      }
    }
    throw new IllegalStateException(
        "Failed to create directory within "
            + TEMP_DIR_ATTEMPTS
            + " attempts (tried "
            + baseName
            + "0 to "
            + baseName
            + (TEMP_DIR_ATTEMPTS - 1)
            + ')');
  }

i
immoteous

I got the same problem so this is just another answer for those who are interested, and it's similar to one of the above:

public static final String tempDir = System.getProperty("java.io.tmpdir")+"tmp"+System.nanoTime();
static {
    File f = new File(tempDir);
    if(!f.exists())
        f.mkdir();
}

And for my application, I decided that to add in a option to clear the temp on exit so I added in a shut-down hook:

Runtime.getRuntime().addShutdownHook(new Thread() {
        @Override
        public void run() {
            //stackless deletion
            String root = MainWindow.tempDir;
            Stack<String> dirStack = new Stack<String>();
            dirStack.push(root);
            while(!dirStack.empty()) {
                String dir = dirStack.pop();
                File f = new File(dir);
                if(f.listFiles().length==0)
                    f.delete();
                else {
                    dirStack.push(dir);
                    for(File ff: f.listFiles()) {
                        if(ff.isFile())
                            ff.delete();
                        else if(ff.isDirectory())
                            dirStack.push(ff.getPath());
                    }
                }
            }
        }
    });

The method delete all subdirs and files before deleting the temp, without using the callstack (which is totally optional and you could do it with recursion at this point), but I want to be on the safe side.


C
Carsten Engelke

As you can see in the other answers, no standard approach has arisen. Hence you already mentioned Apache Commons, I propose the following approach using FileUtils from Apache Commons IO:

/**
 * Creates a temporary subdirectory in the standard temporary directory.
 * This will be automatically deleted upon exit.
 * 
 * @param prefix
 *            the prefix used to create the directory, completed by a
 *            current timestamp. Use for instance your application's name
 * @return the directory
 */
public static File createTempDirectory(String prefix) {

    final File tmp = new File(FileUtils.getTempDirectory().getAbsolutePath()
            + "/" + prefix + System.currentTimeMillis());
    tmp.mkdir();
    Runtime.getRuntime().addShutdownHook(new Thread() {

        @Override
        public void run() {

            try {
                FileUtils.deleteDirectory(tmp);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    });
    return tmp;

}

This is preferred since apache commons the library that comes as closest to the asked "standard" and works with both JDK 7 and older versions. This also returns an "old" File instance (which is stream based) and not a "new" Path instance (which is buffer based and would be the result of JDK7's getTemporaryDirectory() method) -> Therefore it returns what most people need when they want to create a temporary directory.


D
DigitShifter

Try this small example:

Code:

try {
    Path tmpDir = Files.createTempDirectory("tmpDir");
    System.out.println(tmpDir.toString());
    Files.delete(tmpDir);
} catch (IOException e) {
    e.printStackTrace();
}

Imports: java.io.IOException java.nio.file.Files java.nio.file.Path

Console output on Windows machine: C:\Users\userName\AppData\Local\Temp\tmpDir2908538301081367877

Comment:
Files.createTempDirectory generates unique ID atomatically - 2908538301081367877.

Note:
Read the following for deleting directories recursively:
Delete directories recursively in Java


b
bluish

I like the multiple attempts at creating a unique name but even this solution does not rule out a race condition. Another process can slip in after the test for exists() and the if(newTempDir.mkdirs()) method invocation. I have no idea how to completely make this safe without resorting to native code, which I presume is what's buried inside File.createTempFile().


g
geri

Before Java 7 you could also:

File folder = File.createTempFile("testFileUtils", ""); // no suffix
folder.delete();
folder.mkdirs();
folder.deleteOnExit();

Nice code. But unfortunatelly "deleteOnExit()" won't work, since Java can't delete the whole folder at once. You have to delete all files recursively :/
o
ordnungswidrig

Using File#createTempFile and delete to create a unique name for the directory seems ok. You should add a ShutdownHook to delete the directory (recursively) on JVM shutdown.


A shutdown hook is cumbersome. Wouldn't File#deleteOnExit also work?
#deleteOnExit didn't work for me - I believe it won't delete non-empty directories.
I implemented a quick test running with Java 8, but temp folder was not deleted, see pastebin.com/mjgG70KG