A reason to prefer Streams over for is that for does everything. You get to use different names for different operations, rather than having to recognise a pattern spread across tens of lines.
The problem with both versions is doing everything in one method. You are missing functions like
Stream<T> <T> notNull(Stream<T> objects) {
return objects.filter(o -> o != null);
}
Stream<Pair<Folder, Folder>> getFolderPairs(Project project) {
return project.getFolders().stream()
.map(folder ->
Pair.of(folder, project.getGeneratorSource())
);
}
boolean isNotSourceFolder(Pair<Folder, Folder> folders) {
return !folders.getLeft().equals(folders.getRight());
}
Stream<Folder> subfoldersFor(Stream<Project> projects) {
return notNull(projects)
.flatMap(getFolderPairs)
.filter(isNotSourceFolder)
.map(Pair::getLeft)
}
boolean canGenerateImages(Folder folder) {
return (folder.getDerivative().isPresent()
|| folder.getDpi().isPresent()
|| folder.getImageScale().isPresent()
|| folder.getImageSize().isPresent())
}
Note that I use sensible names for the parameters of my lambda expressions, rather than lambda everywhere.
Using those, your original function becomes much simpler, even with a formatter that wants things together.
return subfoldersFor(projects.parallelStream())
.anyMatch(canGenerateImages);