Hello David,
You mean something like this?
void main() throws Exception {
final Path root = Path.of(System.getProperty("user.home"));
IO.println(root);
IO.println(root.toAbsolutePath());
Stream
.of(root)
.flatMap(new Function<Path, Stream<Path>>() {
@Override public Stream<Path> apply(Path p) {
if (Files.isRegularFile(p)) {
return Stream.of(p);
} else {
try {
return Files.list(p).flatMap(this::apply);
} catch (IOException _) {
return Stream.empty();
}
}
}
})
.limit(5)
.forEach(IO::println);
}
On 2026-08-26 16:24, David Alayachew wrote:
Hello Viktor and Rémi,
First off, sorry for the horrifically delayed response. Juggling disasters
and emergencies.
Rémi, thanks for the example with mapMulti. That has been my temporary
workaround for now, and while it is still not ideal, it is better than
where I was before.
Viktor, sure, here is a simple example -- traversing a directory tree, and
only passing the files down the stream.
I actually made a post on StackOverflow --
https://softwareengineering.stackexchange.com/questions/461442/
But anyways, when attempting to do this with streams, this is where I
started.
import module java.base;
void main() throws Exception
{
final Path root = Path.of(System.getProperty("user.home"));
IO.println(root);
IO.println(root.toAbsolutePath());
Stream
.of(root)
.mapMulti(this::recursiveDescent)
.limit(5)
.forEach(IO::println)
;
}
private void recursiveDescent(final Path rootPath, final Consumer<Path>
downstream)
{
final Stack<Path> stack = new Stack<>();
stack.push(rootPath);
while (!stack.empty())
{
final Path path = stack.pop();
if (Files.isRegularFile(path))
{
downstream.accept(path);
}
else
{
try (final Stream<Path> folderContents = Files.list(path))
{
folderContents.forEach(stack::push);
}
catch (final Exception exception)
{
throw new IllegalStateException("Failed for " + path,
exception);
}
}
}
}
But this has at least 2 major downsides.
1 - This is not easy to turn parallel (comparatively).
2 - This does not short-circuit when the downstream no longer accepts
elements.
Ok, I can at least solve problem 2 by becoming a Gatherer instead.
Here is my gatherer attempt.
import module java.base;
void main() throws Exception
{
final Path root = Path.of(System.getProperty("user.home"));
IO.println(root);
IO.println(root.toAbsolutePath());
final Gatherer<Path, Stack<Path>, Path> gatherer =
Gatherer
.of
(
Stack<Path>::new,
(stack, rootPath, downstream) ->
{
stack.push(rootPath);
while (!stack.isEmpty())
{
final Path path = stack.pop();
if (Files.isRegularFile(path))
{
final boolean acceptingMoreElements =
downstream.push(path);
if (!acceptingMoreElements)
{
return false;
}
}
else
{
try (final Stream<Path> folderContents =
Files.list(path))
{
folderContents.forEach(stack::push);
}
catch (final Exception exception)
{
throw new IllegalStateException("Failed for " +
path, exception);
}
}
}
return true;
},
(s1, s2) ->
{
s1.addAll(s2);
return s1;
},
(stack, downstream) ->
{
for (final Path path : stack)
{
if (!downstream.push(path))
{
return;
}
}
}
)
;
Stream
.of(root)
.gather(gatherer)
.limit(5)
.forEach(IO::println)
;
}
So, problem 2 is solved, but problem 1 is not really. Sure, I could turn my
stream parallel, but the actual meat of the processing is sequential when
it really doesn't need to be.
Let me know if this makes more sense. And sorry, you might have to scroll
to read earlier emails in this thread to get the context. I know it was
several months back.
On Fri, Nov 14, 2025 at 5:42 AM Remi Forax <[email protected]> wrote:
Hi David,
You can always transform an imperative code to a stream by pushing the
element through a consumer.
Internally, a stream uses a push iterator (see
Spliterator.tryAdvance(consumer)).
As a silly example, this is a way to write fibonacci (the recursive form)
with a stream right in the middle.
static void fibo(int n, IntConsumer consumer) {
if (n < 2) {
consumer.accept(n);
return;
}
var result = Stream.of("")
.mapMultiToInt((_, consumer2) -> {
fibo(n - 1, consumer2);
fibo(n - 2, consumer2);
})
.sum();
consumer.accept(result);
}
static void main() {
fibo(7, IO::println);
}
Here, I use mapMulti() to convert the imperative code to a Stream
(there is no factory method on Stream that takes a consumer of consumer).
If you also want to short-circuit, you can use a gatherer instead of
mapMulti but short-circuiting the recursive code will require to use an
exception as control flow (it will not be pretty).
regards,
Rémi
------------------------------
*From: *"David Alayachew" <[email protected]>
*To: *"core-libs-dev" <[email protected]>
*Sent: *Tuesday, November 11, 2025 4:36:29 AM
*Subject: *Difficulties of recursion with Streams
Hello @core-libs-dev <[email protected]>,
When working with streams, I often run into situations where I have to
"demote" back to imperative code because I am trying to solve a problem
best solved by recursion.
Consider the common use case of cycling through permutations to find all
permutations that satisfy some condition. With recursion, the answer is
incredibly simple -- just grab an element from the set, then call the
recursive method with a copy of the set minus the grabbed element. Once you
reach the empty set, you've reached your terminal condition.
Use cases like that are not only incredibly common, but usually,
embarrassingly parallel. The example above of cycling through permutations
is only a few lines of imperative code, but I struggle to imagine how I
would do this with Streams.
I guess let me start by asking -- are there any good ways currently to
accomplish the above permutation example with Streams? And if not, should
there be?
Thank you for your time and consideration.
David Alayachew
--
Cheers,
√
Viktor Klang
Software Architect, Java Platform Group
Oracle