code: public static void copyDirectory(final Path source, final Path destination, List<Path> excludes)
        throws IOException {
      Preconditions.checkNotNull(source);
      Preconditions.checkNotNull(destination);
      Preconditions.checkArgument(Files.isDirectory(source), "Source is not a directory");
      Preconditions.checkArgument(Files.isDirectory(destination), "Destination is not a directory");
      Preconditions.checkArgument(
          !Files.isSameFile(source, destination), "Source and destination are the same");
      Preconditions.checkArgument(
          !destination.toAbsolutePath().startsWith(source.toAbsolutePath()),
          "destination is child of source");
  
      Files.walkFileTree(
          source,
          new SimpleFileVisitor<Path>() {
            final CopyOption[] copyOptions = new CopyOption[] {StandardCopyOption.COPY_ATTRIBUTES};
  
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                throws IOException {
  
              if (dir.equals(source)) {
                return FileVisitResult.CONTINUE;
              }
  
              if (excludes.contains(dir)) {
                return FileVisitResult.SKIP_SUBTREE;
              }
  
              Files.copy(dir, destination.resolve(source.relativize(dir)), copyOptions);
              return FileVisitResult.CONTINUE;
            }
  
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                throws IOException {
  
              if (excludes.contains(file)) {
                return FileVisitResult.CONTINUE;
              }
  
              Files.copy(file, destination.resolve(source.relativize(file)), copyOptions);
              return FileVisitResult.CONTINUE;
            }
          });
    }
docs: Implementation of recursive directory copy, does NOT overwrite.
  
  @param source an existing source directory to copy from
  @param destination an existing destination directory to copy to
  @param excludes a list of paths in "source" to exclude
  @throws IllegalArgumentException if source directory is same destination directory, either
  source or destination is not a directory, or destination is inside source