62

Java IO has File.deleteOnExit(), which is a method that deletes the file it is called on during normal termination of the JVM. I've found this to be very useful for cleaning up temporary files, especially during unit tests.

However, I don't see a method by the same name in Java NIO's Files class. I am aware that I can do path.toFile().deleteOnExit(), but I'd like to know if there is an alternative using NIO.

Is there an alternative? If not, why is there not one?

Thunderforge
  • 19,637
  • 18
  • 83
  • 130
  • 1
    What would the NIO version do which is different or better? (Apart from dropping `.toFile()` from the call chain.) – Peter Lawrey Feb 26 '15 at 20:24
  • Since NIO is essentially dealing with the actual access to the file, is there a reason that `Path.toFile()` isn't suitable? – chrylis -cautiouslyoptimistic- Feb 26 '15 at 20:24
  • You mentioned unit tests. Look into junit's `TemporaryFolder` for that. – David Ehrmann Feb 26 '15 at 20:33
  • @PeterLawrey Dropping the `.toFile()` and living entirely in NIO is one benefit. Also, Oracle describes the File class as part of [Legacy File I/O Code](http://docs.oracle.com/javase/tutorial/essential/io/legacy.html), which suggests to me that it may one day be deprecated (it may not given that Java deprecation happens very slowly, but I figure I might as well future-proof it if I can). – Thunderforge Feb 26 '15 at 20:34
  • @DavidEhrmann Thanks, that should be really helpful. It would still be good to answer the question for general use. – Thunderforge Feb 26 '15 at 20:36
  • 1
    @Thunderforge DataInputStream.readLine() was actually `@Deprecated` in 1998, it is still there. What are the benefits of "living entirely in NIO"? – Peter Lawrey Feb 26 '15 at 20:48
  • 5
    @PeterLawrey I'm aware that Java tends to not remove things that they deprecate (which seems bizarre to me coming from Python), but they could one day. As for "living entirely in NIO", the code would be easier to grasp if it is entirely in NIO instead of using both (especially for younger developers who might have started with NIO and would never have used IO before). Regardless of whether or not you agree with the rationale, I'd still like to know if there is an alternative. – Thunderforge Feb 26 '15 at 20:58
  • 3
    @Thunderforge the point I was trying to make is that in Java they avoid adding things unless there is a compelling reason to do some. Every method they add is considered very carefully and if all it would do is much the same as an existing one, I suspect it won't happen. – Peter Lawrey Feb 26 '15 at 21:01
  • @Thunderforge As you can see from BloodShura's answer it can be done entirely in NIO, but is that really simpler, I don't know. – Peter Lawrey Feb 26 '15 at 21:01
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/71831/discussion-between-thunderforge-and-peter-lawrey). – Thunderforge Feb 26 '15 at 21:04
  • 3
    'Living entirely in NIO' isn't a benefit, it's just an arbitrary self-imposed constraint. – user207421 Feb 26 '15 at 21:07

3 Answers3

43

Short Answer

You can't delete arbitrary files in Java NIO, but you can use the StandardOpenOption.DELETE_ON_CLOSE when opening a new stream, which will delete the file as soon as the stream closes, either by calling .close() (including from a try-with-resources statement) or the JVM terminating. For instance:

Files.newOutputStream(Paths.get("Foo.tmp"), StandardOpenOption.DELETE_ON_CLOSE);

Long Answer

After a great deal of digging around, I found that Java NIO does have a way to delete on exit, but it approaches it in a different way that Java I/O.

First off, the Javadoc for Files.createTempFile() describes three ways to delete files:

Where used as a work files [sic], the resulting file may be opened using the DELETE_ON_CLOSE option so that the file is deleted when the appropriate close method is invoked. Alternatively, a shutdown-hook, or the File.deleteOnExit() mechanism may be used to delete the file automatically.

The last choice, File.deleteOnExit() is of course a Java I/O method, which we are trying to avoid. A shutdown-hook is what is happening behind the scenes when you call the aforementioned method. But the DELETE_ON_CLOSE option is pure Java NIO.

Rather than deleting arbitrary files, Java NIO assumes that you are only interested in deleting files that you are actually opening. Therefore, methods that create a new stream such as Files.newOutputStream() can optionally take several OpenOptions, where you can input StandardOpenOption.DELETE_ON_CLOSE. What that does is delete the file as soon as the stream is closed (either by a call to .close() or the JVM exiting).

For instance:

Files.newOutputStream(Paths.get("Foo.tmp"), StandardOpenOption.DELETE_ON_CLOSE);

...will delete the file associated with the stream when the stream is closed, either from an explicit call to .close(), the stream being closed as part of a try-with-resources statement, or the JVM terminating.

Update: On some operating systems such as Linux, StandardOpenOption.DELETE_ON_CLOSE deletes as soon as the OutputStream is created. If all you need is one OutputStream, that may still be okay. See DELETE_ON_CLOSE deletes files before close on Linux for more info.

So Java NIO adds new functionality over Java I/O in that you can delete a file when closing a stream. If that's a good enough alternative to deleting during JVM exit, you can do it in pure Java NIO. If not, you'll have to rely on Java I/O's File.deleteOnExit() or a shutdown-hook to delete the file.

Community
  • 1
  • 1
Thunderforge
  • 19,637
  • 18
  • 83
  • 130
  • 7
    This is a dangerous answer; `DELETE_ON_CLOSE` is not a drop-in replacement for `File.deleteOnExit()` because it forces you to keep the output stream open for as long as you want to work with the file. Also, the file is actually [deleted immediately on some platforms](http://stackoverflow.com/questions/18146637/delete-on-close-deletes-files-before-close-on-linux). – daiscog Mar 29 '17 at 10:21
  • @megaflop I noted in my answer that `DELETE_ON_CLOSE` works differently in that it will "delete the file as soon as the stream is closed", so I thought that I already made it clear that it doesn't work the same way as `File.deleteOnExit()`. If you are okay with it deleting right away (which in my use cases, I was), then that's a fine replacement. – Thunderforge Mar 29 '17 at 17:28
  • 2
    If it's not the same then the question is not answered. – Simon Jenkins Mar 11 '19 at 14:30
25

Behind the scenes, File.deleteOnExit() will just create a shutdown hook via Runtime.addShutdownHook().

Then, you can do the same thing with NIO:

Runtime.getRuntime().addShutdownHook(new Thread() {
  public void run() {
    Path path = ...;

    Files.delete(path);
  }
});
  • 2
    +1 It seems in the accepted answer the shutdown hook gets coupled to the `OutputStream`, so if you intent to return the `Path` from the method where the temporary file gets created - you will find that your temporary file has been deleted – dutoitns May 02 '16 at 08:11
10

I would not suggest shoe-horning StandardOpenOption.DELETE_ON_CLOSE into a replacement for File.deleteOnExit(). As the documentation mentions it's neither intended to be general-purpose, nor is it likely to work correctly outside of trivial cases.

DELETE_ON_CLOSE, as the name implies, is designed to be used to remove a file once it's closed to immediately clean up a no-longer-needed resource. The documentation for Files.createTempFile() is similarly clear on this point, DELETE_ON_CLOSE can be used for "work files" only needed while the file is open.

The Files.createTempFile() docs suggest directly either writing your own shutdown hook or simply continuing to use File.deleteOnExit(). Despite your desire to use NIO there's nothing inherently wrong with using File.deleteOnExit() if you're only working with the local filesystem. If you aren't using (or aren't certain you're using) the local filesystem and therefore can't use File.deleteOnExit() it's straightforward enough to write your own shutdown hook just like what File does:

public final class DeletePathsAtShutdown {
  private static LinkedHashSet<Path> files = new LinkedHashSet<>();

  static {
    Runtime.getRuntime().addShutdownHook(
        new Thread(DeletePathsAtShutdown::shutdownHook));
  }

  private static void shutdownHook() {
    LinkedHashSet<Path> local;
    synchronized {
      local = paths;
      paths = null;
    }

    ArrayList<Path> toBeDeleted = new ArrayList<>(theFiles);
    Collections.reverse(toBeDeleted);
    for (Path p : toBeDeleted) {
      try {
        Files.delete(p);
      } catch (IOException | RuntimeException e) {
        // do nothing - best-effort
      }
    }
  }

  public static synchronized void register(Path p) {
    if (paths == null) {
      throw new IllegalStateException("ShutdownHook already in progress.");
    }
    paths.add(p);
  }
}

Sure, it might be nice if NIO included a similar shutdown hook out of the box, but its absence is no reason to use the wrong tool for the job. You can also add more functionality to DeletePathsAtShutdown, such as a remove() function, or support for deleting directories.

Community
  • 1
  • 1
dimo414
  • 47,227
  • 18
  • 148
  • 244