Search code examples
javafilefilesystems

Count files in folder, ignoring hidden files, ignoring nested folders, in Java


Simply counting files is turning out to be surprisingly difficult in Java.

I want to point to a folder, and count files found at the top level of that folder. Ignore hidden files, such as Unix-style names starting with a .. Ignore nested folders.

Example:

📂 Sales 
    📂 Wine Sales 2024 
        📄 .DS_Store
        📄 Interim report.text
        📂 Q1
            📄 West region.text
            📄 East region.text
        📂 Q2
        📂 Q3
        📂 Q4

If I point to the folder named "Wine Sales 2024", I want to get a count of 1, for a count of files found directly in that folder. In this case, Interim report.text. I want to ignore the folders within. I do not want a recursive count of any documents found within those nested folders.

File#listFiles

I found the Question, Counting the number of files in a directory using Java. But that Question is actually asking about performance issues, and I do not care about performance issues. I have simple folders with maybe a dozen documents; I just want an easy way to get the count.

That Question has some awkward code that does not meet my basic criteria. This line:

new File(<directory path>).listFiles().length

… for example:

 final int countFilesFound = Objects.requireNonNull( path.toFile( ).listFiles( ( folder , name ) -> !name.startsWith( "." ) ) ).length;

… has a few issues:

  • ❌ Counts nested folders as files. I want to ignore nested folders.
  • ✅ As for ignoring hidden files, I can pass a lambda ( folder , name ) -> !name.startsWith( "." ) which does work.
  • ❌ I have to convert my Path object to a File to make this work, which seems silly to me, but no big deal.
  • ❌ Is indirect and complicated, having to create an array and then ask for its length.

Files.list

This Answer shows code using Java NIO features of Java 8+.

try (Stream<Path> files = Files.list(Paths.get("your/path/here"))) {
    long count = files.count();
}

I adapted that to this partial solution:

final long count;
try
{
    count = Files.list( path ).filter( Files :: isRegularFile ).count( );
}
catch ( IOException e )
{
    throw new RuntimeException( e );
}

Issues include:

  • ❌ Having to wrap in the try-catch. Workable, but wow so many lines for such a simple goal of counting files.
  • ❌ The .list call has a warning in my IDE that I need a try-with-resources here. I cannot understand needing to call close on a stream that has a terminal operation (.count here). I have never seen that issue in all my time using streams.
  • ✅ Successfully ignores folders.

Is there any simple way to get a list and/or count of real files found directly in a folder?


Solution

  • You can't really escape exception handling here (unless the API swallows exception, like the old one), operations with files and folders can always fail.

    Technically you could not deal with the exceptions, using lomboks @SneakyThrows or something similar, but it's better to handle it, an IOException is a real possibility.

    The docs explicitly state that streams, whose source is an IO channel must be closed:

    Streams have a BaseStream.close() method and implement AutoCloseable, but nearly all stream instances do not actually need to be closed after use. Generally, only streams whose source is an IO channel (such as those returned by Files.lines(Path, Charset)) will require closing. Most streams are backed by collections, arrays, or generating functions, which require no special resource management. (If a stream does require closing, it can be declared as a resource in a try-with-resources statement.)

    Your best bet is try-with-resources to close the stream and handle the exception together:

    final long count;
    try (Stream<Path> stream = Files.list(Path.of("some path"))) {
      count = stream
              .peek(path -> System.out.println("debug current path - " + path))
              .filter(Files::isRegularFile)
              .count();
      System.out.println(count);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
    

    You could combine the old API with streams, in order to not handle the exception, but that's probably not good idea, because it will be swallowed instead, all File.listFiles() methods do so:

    Returns: An array of abstract pathnames denoting the files and directories in the directory denoted by this abstract pathname. The array will be empty if the directory is empty. Returns null if this abstract pathname does not denote a directory, or if an I/O error occurs.

    final long countFilesFound = Arrays.stream(Objects.requireNonNull(path.toFile().listFiles((folder, name) -> !name.startsWith("."))))
                .filter(File::isFile)
                .count();
    

    Also, this approach is less readable, IMHO.