If they are already in the list, a stream is not required; just attach a sublist of everything except the last element, and separate the other separator and the final element:
int last = list.size() - 1; String joined = String.join(" and ", String.join(", ", list.subList(0, last)), list.get(last));
Here is the version that does this using Collectors.collectingAndThen:
stream.collect(Collectors.collectingAndThen(Collectors.toList(), joiningLastDelimiter(", ", " and "))); public static Function<List<String>, String> joiningLastDelimiter( String delimiter, String lastDelimiter) { return list -> { int last = list.size() - 1; if (last < 1) return String.join(delimiter, list); return String.join(lastDelimiter, String.join(delimiter, list.subList(0, last)), list.get(last)); }; }
This version can also handle the case when the stream is empty or has only one value. Thanks to Holger and Andreas for their suggestions that greatly improved this solution.
I suggested in a comment that the Oxford comma can be executed using ", " and ", and" as delimiters, but this leads to incorrect "a, and b" results for the two elements, so just for fun here, Oxford writes correctly :
stream.collect(Collectors.collectingAndThen(Collectors.toList(), joiningOxfordComma())); public static Function<List<String>, String> joiningOxfordComma() { return list -> { int last = list.size() - 1; if (last < 1) return String.join("", list); if (last == 1) return String.join(" and ", list); return String.join(", and ", String.join(", ", list.subList(0, last)), list.get(last)); }; }