Jackson Java Spring

Searching for files in Java 8 using Files.find() method, streams and lambda expressions

Since Java 8 we have a new way of finding files in a file tree using find() method from Files class. This class contains a set of static methods used for operating on files, directories, or other types of files. In this post we will implement functionality of finding files which were modified given number days ago.

The signature of the Files.find()method is following:

public static Stream<Path> find(Path start,
                                int maxDepth,
                                BiPredicate<Path,BasicFileAttributes> matcher,
                                FileVisitOption... options) throws IOException
Parameter name Description
startthe starting file
maxDepth the maximum number of directory levels to search
matcher the function used to decide whether a file should be included in the returned stream
optionsoptions to configure the traversal

The most interesting parameter is matcher. It is a functional interface called BiPredicate with type parameters Path and BasicFileAttributes. According to the javadoc, “the Path object is obtained as if by resolving the relative path against start and is only included in the returned Stream if the BiPredicate returns true“. The BasicFileAttributes object gives us access to many file attributes which we may use for filtering files we search for. Let’s implement our find functionality using TDD approach.

Unit test

We would like to test the functionality of finding files which last modified time was more than two days ago. We will search one directory only (maxDepth == 1). We will use TemporaryFolder JUnit Rule. “The TemporaryFolder Rule allows creation of files and folders that are deleted when the test method finishes (whether it passes or fails). By default no exception is thrown if resources cannot be deleted”.

@Rule
public TemporaryFolder tempTestFolder = new TemporaryFolder();

In our test we will create in the temporary folder four files and we will set last modified time to different days.

final File fileModifiedOneDayAgo = tempTestFolder.newFile("testFile1");
fileModifiedOneDayAgo.setLastModified(daysAgoInMillis(1));
    
final File fileModifiedTwoDaysAgo = tempTestFolder.newFile("testFile2");
fileModifiedTwoDaysAgo.setLastModified(daysAgoInMillis(2));
    
final File fileModifiedFourDaysAgo = tempTestFolder.newFile("testFile4");
fileModifiedFourDaysAgo.setLastModified(daysAgoInMillis(4));
    
final File fileModifiedFiveDaysAgo = tempTestFolder.newFile("testFile5");
fileModifiedFiveDaysAgo.setLastModified(daysAgoInMillis(5));

Now let’s call our file finding method giving as parameters the path to the temporary folder and the number of days. Our file finding method should return a list of Files which were modified later than given number of days. We will omit time during the searching.

final FileFinder fileFinder = new FileFinder();
final List<File> files = fileFinder.findFilesModifiedDaysAgo(asPath(tempTestFolder), 2);

Finally, let’s write some assertions. We have two files modified later than two days, one file modified one day ago and the one modified exactly two days ago. In this case we assume that number of found files should be 2. We expect our find method will return two files modified four and five days ago.

assertTrue(files.size() == 2);
    
assertTrue(files.contains(fileModifiedFourDaysAgo));
assertTrue(files.contains(fileModifiedFiveDaysAgo));
    
assertFalse(files.contains(fileModifiedOneDayAgo));
assertFalse(files.contains(fileModifiedTwoDaysAgo));

Implementation

The Files.find() method returns stream of Paths. We need to remember that “the returned stream encapsulates one or more DirectoryStreams. If timely disposal of file system resources is required, the try-with-resources construct should be used to ensure that the stream’s close method is invoked after the stream operations are completed. Operating on a closed stream will result in an IllegalStateException“. The implementation of our matcher function passed to find() method will be following:

Files.find(start, 1, (path, attribute) -> {
  final long modifiedMillis = attribute.lastModifiedTime().toMillis(); 
  final LocalDateTime modifiedDate = getLocalDateTimeForMillis(modifiedMillis);
  final LocalDateTime daysAgoDate = getLocalDateTimeForMillis(daysAgoMillis);
  return modifiedDate.truncatedTo(ChronoUnit.DAYS).compareTo(daysAgoDate.truncatedTo(ChronoUnit.DAYS)) < 0;
})

In the matcher function we will compare modification time of all found files in given directory with the given number of days. Both files modification time and the number of days will be converted to LocalDateTime object and truncated to days. We will be able to filter out files using days only and omitting time.

The Files.find() method returns stream of Paths but we would like to return List of Files. Let’s map Paths to Files then and collect all found files into the List.

return files.map(path -> path.toFile()).collect(Collectors.toList());

The final class with our file finding method will be following:

public class FileFinder {

  public List<File> findFilesModifiedDaysAgo(Path start, int numberOfDays) throws IOException {
    final long dayInMillis = 24 * 60 * 60 * 1000;
    final long daysAgoMillis = System.currentTimeMillis() - numberOfDays * dayInMillis;
    
    try (final Stream<Path> files = Files.find(start, 1, (path, attribute) -> {
      final long modifiedMillis = attribute.lastModifiedTime().toMillis(); 
      final LocalDateTime modifiedDate = getLocalDateTimeForMillis(modifiedMillis);
      final LocalDateTime daysAgoDate = getLocalDateTimeForMillis(daysAgoMillis);
      return modifiedDate.truncatedTo(ChronoUnit.DAYS).compareTo(daysAgoDate.truncatedTo(ChronoUnit.DAYS)) < 0;
    })) {
      return files.map(path -> path.toFile()).collect(Collectors.toList());
    }
  }
  
  private LocalDateTime getLocalDateTimeForMillis(final long timeInMillis) {
    return LocalDateTime.ofInstant(Instant.ofEpochMilli(timeInMillis), ZoneId.systemDefault());
  }

}

Conclusion

Since Java 8 the Files.find() method gives us a possibility of finding files using streams. The quite powerful functional interface in the form matcher function allows us for filtering files during searching using our search criteria on the fly. We may use lambda expressions for implementation our matcher function. When all files are filtered out and returned as a Stream of Paths we may map Path to Files and return it as a List. The try-with-resources statement nicely handles stream closing for us.

Download the full example

You may download a Maven project from here or just clone the github repository.

FileFinder example (23 downloads)