Side-effects
Stream.forEach()
operation should be utilized with care since it operates via side-effects and should not be used as a substitution of a proper reduction operation.
The way you've written this stream is discouraged by the Stream API documentation because it makes code more cluttered and difficult to follow and more importantly your solution is broken with parallel streams (there should be no assumptions on the nature of the stream in your code).
In this particular case, you should be using Stream.toArray()
operation instead.
Multiline lambdas
Try to void them. They bear a lot of cognitive load. If a lambda expression requires several lines or when you have a complex single-line lambda (e.g. with a nested stream in it), consider introducing a method.
Exceptions
In short, the purpose of exceptions is to indicate cases when it's not possible to proceed with the normal execution flow.
If you stumbled on a corrupt piece of data which violates invariant that are important for your business logic, usually you don't want to proceed processing it. That's a valid case to throw. You asked can you "throw from a stream"? Sure, it's just a means iteration.
I've seen some bickering over whether it's appropriate to use exceptions for the purpose of validation. Sure it is, we do employ Exceptions for Validation for decades.
Unless you're using exceptions to avoid conditional logic, or to make weird hacks like throwing in order to break from a recursive method, and you have a genuine invalid piece of data on your hands you can and should throw.
Another, important note: exceptions should be informative. If standard exception types can describe the case at hand, fine, if not introduce your own exception type.
Also, use proper exception messages that will be helpful in investigating the issue.
Static routines
Don't treat everything as util classes, use the Power of object-orientation to make more clean, cohesive and testable.
Refactored version
public class ArrayParser { private final String separator; private final int columnCount; public ArrayParser(String separator, int columnCount) { this.separator = separator; this.columnCount = columnCount; } public String[][] parse(final String str) { return str.lines() .map(this::parseLine) .toArray(String[][]::new); } private String[] parseLine(String toParse) { String[] line = toParse.split(separator); validateLine(line); return line; } private void validateLine(String[] line) { if (line.length != columnCount) { throw new LineParsingException(line, columnCount); } } }
Exception example:
private class LineParsingException extends RuntimeException { private static final String MESSAGE_TEMPLATE = """ The actual number of columns in the line %s doesn't match the expected number of columns %d"""; public LineParsingException(String[] line, int columnsExpected) { super(MESSAGE_TEMPLATE.formatted(Arrays.toString(line), columnsExpected)); } }
RFC 4180
? In which case this code is obviously simplistic and can't parse a CSV.\$\endgroup\$"bla, \"bl,ah\""
and see why rolling your own CSV parser is almost always a mistake. Also, not all CSV files use a comma as the separator, many non-english countries use a semi-colon.\$\endgroup\$