Introduction

In line with the other posts on working with Files and Directories (Java NIO Files, NIO Files operating on directories, and Apache Commons IOUtils), the last post was about Apache FileUtils working with files. This post is the second part of it - Apache Common FileUtils directory operations.

Apache Commons IO FileUtils Directory operations

In this post, we will cover the following directory manipulation operations using Apache Commons FileUtils.

  • Creating a directory
  • Copying and moving a directory
  • Listing and iterating over a directory
  • Checking if a directory contains a file
  • Finding the size of a directory
  • Cleaning and Deleting a directory

Creating a directory

forceMkdir

FileUtils.forceMkdir makes a directory including all parent directories (if not present already).

File directory = new File("/Users/JavaDevCentral/data/new-directory");
FileUtils.forceMkdir(directory);

The above will create a new directory called new-directory inside the data folder.

If a directory already exists, it will not do anything.

If a file already exists with the specified name, it will throw an IOException. Let us say we have a file named file inside the data directory and we attempt to create a directory named file.

File directory = new File("/Users/JavaDevCentral/data/file");
FileUtils.forceMkdir(directory);

/*
 Throws
 java.io.IOException: File /Users/JavaDevCentral/data/file exists and is not a directory. 
 Unable to create directory.
 */

As mentioned earlier, forceMkDir will create all the non-existing parent directories.

Example: We create a directory path path1/path2 inside the data directory. It will create both the directories (path1 and path2).

File directory = new File("/Users/JavaDevCentral/data/path1/path2");
FileUtils.forceMkdir(directory);

forceMkdirParent

The forceMkdirParent method in Apache Commons FileUtils will create all nonexistent parent directories for a given file. If the directory cannot be created for some reason, it will throw an IOException.

The difference between forceMkdir and forceMkdirParent is that forceMkdir will create the directory specified by the path, whereas forceMkdirParent will only create the parent directory.

File directory = new File("/Users/JavaDevCentral/data/path1/path2");
FileUtils.forceMkdirParent(directory);

In the above code, it will create only the path1 directory (upto the parent of the path specified). It will not do anything if the specified parent directory already exists.

Copying a directory

There are three methods related to copying a directory

  • copyDirectory
  • copyDirectoryToDirectory
  • copyToDirectory

For this section, assume we have the below directory tree

..
|-- data
    |-- dir
        |-- file1
        |-- nested-dir
            |-- file2
            |-- file3.txt

copyDirectory

This method will copy the contents of a directory to the target directory while preserving the file dates. The copy will recursively copy all the subdirectories as well to the destination.

If the destination does not exist, it will create it. If it already exists, it will merge the source with the destination (with the source taking precedence).

The copy will try to preserve the last modified time, but it is not guaranteed.

File sourceDirectory = new File("/Users/JavaDevCentral/data/dir");
File destinationDirectory = new File("/Users/JavaDevCentral/data/targetDirectory");

FileUtils.copyDirectory(sourceDirectory, destinationDirectory);

We are copying the directory contents of dir to a targetDirectory. This will create targetDirectory and copies the contents of dir into it. The resulting tree looks like:

..
|-- data
    |-- dir
        |-- file1
        |-- nested-dir
            |-- file2
            |-- file3.txt
    |-- targetDirectory*
        |-- file1
        |-- nested-dir
            |-- file2
            |-- file3.txt

*newly created

Note: If the source or destination is a file, it fails with an IOException.

Let us say that the destination folder already exists. In that case, the copy will merge into it i.e., it will not delete any other files present in the destination. But if a file with the same name exists in the destination as in the source, it will overwrite it.

Another version (overload) of copyDirectory has a boolean flag called preserveFileDate. Setting it to true will try to preserve the copied files’ last modified date but it cannot be guaranteed to succeed.

Copying directory with FileFilter

We can pass a FileFilter to the copyDirectory and the files that match the filter condition will only be copied.

File destinationDirectory = new File("/Users/JavaDevCentral/data/targetDirectory");
FileUtils.copyDirectory(sourceDirectory, destinationDirectory, DirectoryFileFilter.DIRECTORY);

The FileFilter DirectoryFilter.DIRECTORY copies only the directory.

Using FileFilterUtils.prefixFileFilter, we can filter the files that have a particular prefix.

//Copies only file1
FileUtils.copyDirectory(sourceDirectory, destinationDirectory, 
    FileFilterUtils.prefixFileFilter("file"));
FileUtils.copyDirectory(sourceDirectory, destinationDirectory, FileFilterUtils.or(
        FileFilterUtils.prefixFileFilter("file"), DirectoryFileFilter.DIRECTORY));

We can combine predicates as shown above using or method. This will copy all directories and files whose name starts with file. In our example, it will copy all the files inside the source directory.

copyDirectoryToDirectory

The copyDirectoryToDirectory differs from copyDirectory in which it copies a directory (the source directory) into the target directory.

It will create the destination if it does not exist. If it exists, it merges the source with the destination.

sourceDirectory = new File("/Users/JavaDevCentral/data/dir");
destinationDirectory = new File("/Users/JavaDevCentral/data/targetDirectory");
FileUtils.copyDirectoryToDirectory(sourceDirectory, destinationDirectory);

It copies the dir directory inside the destination directory. The resulting directory structure is:

..
|-- data
    |-- dir
        |-- file1
        |-- nested-dir
            |-- file2
            |-- file3.txt
    |-- targetDirectory
        |-- dir
            |-- file1
            |-- nested-dir
                |-- file2
                |-- file3.txt

The source has to be directory - else it throws an IllegalArgumentException.

copyToDirectory

The copyToDirectory method works for both files and directories.

  • If the source is a file, it calls copyFileToDirectory.
  • If the source is a directory, it calls copyDirectoryToDirectory

Copying a list of files to a directory

An overloaded copyToDirectory method copies a list of files to the specified destination. It calls copyFileToDirectory for each file in the list.

File destinationDirectory = new File("/Users/JavaDevcentral/data/dest");
List<File> files = Arrays.asList(
        new File("/Users/JavaDevcentral/data/output-file.txt"),
        new File("/Users/JavaDevcentral/data/sample-file.txt"));
FileUtils.copyToDirectory(files, destinationDirectory);

Moving a directory

Similar to copy directory, there are three similar methods

  • moveDirectory
  • moveDirectoryToDirectory
  • moveToDirectory

moveDirectory

It moves a directory from source to destination.

File sourceDirectory = new File("/Users/JavaDevcentral/data/dir");
File destinationDirectory = new File("/Users/JavaDevcentral/data/targetDirectory");
FileUtils.moveDirectory(sourceDirectory, destinationDirectory);

Result,

..
|-- data
    |-- targetDirectory
        |-- file1
        |-- nested-dir
            |-- file2
            |-- file3.txt

moveDirectoryToDirectory

This method moves a directory into another directory. It takes a boolean createDestDir to indicate whether it must create the destination.

File sourceDirectory = new File("/Users/JavaDevcentral/data/dir");
File destinationDirectory = new File("/Users/JavaDevcentral/data/targetDirectory");
FileUtils.moveDirectoryToDirectory(sourceDirectory, destinationDirectory, true);

Result is:

..
|-- data
    |-- targetDirectory
        |-- dir
            |-- file1
            |-- nested-dir
                |-- file2
                |-- file3.txt

Note that it moves the parent directory as well.

If we pass the createDestDir as false and the destination does not exist, it throws a FileNotFoundException.

/*
  Throws java.io.FileNotFoundException: Destination directory '/Users/JavaDevcentral/data/targetDirectory'
  does not exist [createDestDir=false]
*/
File sourceDirectory = new File("/Users/JavaDevcentral/data/dir");
File destinationDirectory = new File("/Users/JavaDevcentral/data/targetDirectory");
FileUtils.moveDirectoryToDirectory(sourceDirectory, destinationDirectory, false);

moveToDirectory

This works on moving both files and directory.

  • If the source is a file, it calls moveFileToDirectory.
  • If the source is a directory, it calls moveDirectoryToDirectory

List and Iterate

We will see how to list and iterate files and directories using FileUtils. For this section, I will use the same directory structure used before.

FileUtils.listFiles

The FileUtils.listFiles finds and lists all files within a directory and (optionally) its sub-directories. It accepts a path to start the search from and two IOFileFilters - one for filtering files and another for filtering the directories. When it iterates over the directory tree, only those files and directories that pass the IOFileFilter passed will be listed. This method returns a Collection<File>.

An IOFileFilter is an interface that we use to filter files. There are many implementations of it within the Apache Commons IO library.

Example: To list only the files inside a directory

File file = new File("/Users/JavaDevCentral/data/dir");
Collection<File> files = FileUtils.listFiles(file, TrueFileFilter.INSTANCE, null);
System.out.println(files);

TrueFileFilter.INSTANCE is a file filter that always returns true, and hence all the files will be listed. Since we passed a null for the Directory filter, it will not include any sub-directories. This outputs,

/Users/JavaDevCentral/data/dir/file1

Let us include sub-directories as well.

files = FileUtils.listFiles(file, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
System.out.println(files);

Outputs,

/Users/JavaDevCentral/data/dir/nested-dir/file3.txt, 
/Users/JavaDevCentraldata/dir/nested-dir/file2, 
/Users/JavaDevCentral/data/dir/file1

If we used a FileFilter to filter the files that start with file,

files = FileUtils.listFiles(file,  FileFilterUtils.prefixFileFilter("file"), TrueFileFilter.INSTANCE);
System.out.println(files);

listFiles with extensions

An overloaded listFiles method accepts an array of extensions. The files that match that will only be returned. It has a boolean parameter called recursive. Pass true to traverse sub-directories.

files = FileUtils.listFiles(file, new String[]{"txt"}, false); 
System.out.println(files);

The above returns an empty list as there are no .txt files inside the directory dir. If we traverse the subdirectories,

files = FileUtils.listFiles(file, new String[]{"txt"}, true);
System.out.println(files);

We get the output as

/Users/JavaDevCentral/data/dir/nested-dir/file3.txt

FileUtils.listFilesAndDirs

This lists files within a given directory and (optionally) its subdirectories. This will include the starting directory in the result.

files = FileUtils.listFilesAndDirs(file, TrueFileFilter.INSTANCE, null);
System.out.println(files);

Outputs,

/Users/JavaDevCentraldata/data/dir
/Users/JavaDevCentraldata/data/dir/file1

And,

files = FileUtils.listFilesAndDirs(file, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
System.out.println(files);

Outputs,

/Users/JavaDevCentraldata/data/dir
/Users/JavaDevCentraldata/data/dir/nested-dir
/Users/JavaDevCentraldata/data/dir/nested-dir/file3.txt
/Users/JavaDevCentraldata/data/dir/nested-dir/file2
/Users/JavaDevCentraldata/data/dir/file1

iterateFiles and iterateFilesAndDirs

The iterateFiles and iterateFilesAndDirs are similar to listFiles and listFilesAndDirs methods respectively. But they return an Iterator<File> rather than a Collection<File>.

Below are the calls to iterateFiles and iterateFilesAndDirs corresponding to the code seen so far for listFiles and listFilesAndDirs. They produce the same output as before and hence not showing it again.

//iterateFiles
Iterator<File> fileIterator = FileUtils.iterateFiles(file, TrueFileFilter.INSTANCE, null);
fileIterator.forEachRemaining(System.out::println);

fileIterator = FileUtils.iterateFiles(file, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
fileIterator.forEachRemaining(System.out::println);

fileIterator = FileUtils.iterateFiles(file,  FileFilterUtils.prefixFileFilter("file"), TrueFileFilter.INSTANCE);
fileIterator.forEachRemaining(System.out::println);

//iterateFiles with extension
fileIterator = FileUtils.iterateFiles(file, new String[]{"txt"}, false);
fileIterator.forEachRemaining(System.out::println);

fileIterator = FileUtils.iterateFiles(file, new String[]{"txt"}, true);
fileIterator.forEachRemaining(System.out::println);

//iterateFilesAndDirs
fileIterator = FileUtils.iterateFilesAndDirs(file, TrueFileFilter.INSTANCE, null);
fileIterator.forEachRemaining(System.out::println);

fileIterator = FileUtils.iterateFilesAndDirs(file, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
fileIterator.forEachRemaining(System.out::println);

Check if a directory contains a file

The directoryContains method checks if a given file is present within a parent directory. It checks the sub-directories as well. Using the same directory tree example,

File directory = new File("/Users/JavaDevCentral/data/dir");
File child = new File("/Users/JavaDevCentral/data/dir/file1");
System.out.println(FileUtils.directoryContains(directory, child)); //true

Checking for nested directories and folders,

child = new File("/Users/JavaDevCentral/data/dir/nested-dir");
System.out.println(FileUtils.directoryContains(directory, child)); //true

child = new File("/Users/JavaDevCentral/data/dir/nested-dir/file3.txt");
System.out.println(FileUtils.directoryContains(directory, child)); //true

child = new File("/Users/JavaDevCentral/data/non-existent-file.txt");
System.out.println(FileUtils.directoryContains(directory, child)); //false

Size of a directory

The sizeOfDirectory method finds the size of a directory recursively. It is the sum of size of all files inside the passed directory.

File directory = new File("/Users/JavaDevCentral/data/a");
System.out.println(FileUtils.sizeOfDirectory(directory));

If we pass an empty directory, the size returned will be 0.

Cleaning and Deleting Directories

Clean Directory

FileUtils.cleanDirectory cleans a directory without deleting it i.e., it removes all the files and sub-directories in the directory we clean without removing the directory itself.

File directory = new File("/Users/JavaDevCentral/data/empty-dir");
FileUtils.cleanDirectory(directory); 

Since we passed an empty directory, it does nothing.

File directory = new File("/Users/JavaDevCentral/data/dir");
FileUtils.cleanDirectory(directory);

The above call removes all files and folders inside the directory dir. The result is we have an empty directory.

If the directory does not exist, it fails with an IllegalArgumentException.

/*
  Throws java.lang.IllegalArgumentException: /Users/JavaDevCentraldata/abcd does not exist
*/
File directory = new File("/Users/JavaDevCentral/data/abcd");
FileUtils.cleanDirectory(directory);

Delete Directory

The deleteDirectory deletes a directory recursively. Unlike cleanDirectory it deletes the entire directory.

File directory = new File("/Users/JavaDevCentral/data/dir");
FileUtils.deleteDirectory(directory);

If the directory does not exist, it throws an IllegalArgumentException like the cleanDirectory method.

Conclusion

Following up on Apache FileUtils file operations, we learn about the directory manipulation/operations using Apache Commons FileUtils. We leant the basic directory operations like creating, copying, moving and deleting a directory. We also saw how to list and iterate over a directory, finding the size of a directory and checking if a directory has a file.

I hope this would have been useful. Check out the other posts on Apache Commons.

References