Add prefix and suffix to Collectors.joining() only if there are multiple items present

Yes, this is possible using a custom Collector instance that will use an anonymous object with a count of items in the stream and an overloaded toString() method:

public String format(Stream<String> stream) {
    return stream.collect(
            () -> new Object() {
                StringJoiner stringJoiner = new StringJoiner(",");
                int count;

                @Override
                public String toString() {
                    return count == 1 ? stringJoiner.toString() : "[" + stringJoiner + "]";
                }
            },
            (container, currentString) -> {
                container.stringJoiner.add(currentString);
                container.count++;
            },
            (accumulatingContainer, currentContainer) -> {
                accumulatingContainer.stringJoiner.merge(currentContainer.stringJoiner);
                accumulatingContainer.count += currentContainer.count;
            }
                         ).toString();
}

Explanation

Collector interface has the following methods:

public interface Collector<T,A,R> {
    Supplier<A> supplier();
    BiConsumer<A,T> accumulator();
    BinaryOperator<A> combiner();
    Function<A,R> finisher();
    Set<Characteristics> characteristics();
}

I will omit the last method as it is not relevant for this example.

There is a collect() method with the following signature:

<R> R collect(Supplier<R> supplier,
              BiConsumer<R, ? super T> accumulator,
              BiConsumer<R, R> combiner);

and in our case it would resolve to:

<Object> Object collect(Supplier<Object> supplier,
              BiConsumer<Object, ? super String> accumulator,
              BiConsumer<Object, Object> combiner);
  • In the supplier, we are using an instance of StringJoiner (basically the same thing that Collectors.joining() is using).
  • In the accumulator, we are using StringJoiner::add() but we increment the count as well
  • In the combiner, we are using StringJoiner::merge() and add the count to the accumulator
  • Before returning from format() function, we need to call toString() method to wrap our accumulated StringJoiner instance in [] (or leave it as is is, in case of a single-element stream

The case for an empty case could also be added, I left it out in order not to make this collector more complicated.