0

I have a block to write data(string) using BufferedWriter, but compiler keeps saying error: unreported exception IOException; must be caught or declared to be thrown this.dataContainer.stream().forEach( line -> writer.write(line) );

The BufferWriter.write() is already inside a try-catch block. Is the error caused by it nested inside if-else? How shall it be written?

void saveData() throws IOException {
        
String filename;
Path filePath; 
BufferedWriter writer;
    
filename = "missionImpossible9.csv";
filePath = this.dirPath.resolve(filename); //make path of this file
writer = Files.newBufferedWriter(filePath); 
        
try {
    if (condition1) {
       this.dataContainer1.stream().forEach( line -> writer.write(line) );
     } else {
       this.dataContainer2.stream().forEach( line -> writer.write(line) );
     }
     writer.close();
} catch (IOException err){
    err.getStackTrace();}
}
5
  • 3
    Change that to try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(filePath))) { ... And printStackTrace btw Commented Jul 1, 2024 at 22:21
  • 1
    By the way, there is a Files.write method which writes a sequence of lines to a file, without your having to create a Writer. As in, Files.write(filePath, this.dataContainer1);. Commented Jul 2, 2024 at 14:34
  • @VGR Files.write() was also raised by Holger. It looks like it takes a Path and an Iterable (ArrayList, Queue, etc), and it will loop through it automatically and write out item by item? Commented Jul 2, 2024 at 15:22
  • 1
    Yes. It’s not identical to your code, since your code does not write a newline after each line. I’m not sure if that was intentional. Commented Jul 2, 2024 at 15:36
  • @VGR thanks, it works better. Each data element in my example dataContainer is csv with newline included. Commented Jul 2, 2024 at 20:57

2 Answers 2

3

The problem is that the method write() of BufferedWriter is declared as throwing IOException, but you're calling it inside a Consumer<?> (the .forEach()) which is not supposed to throw any checked exception:

this.dataContainer1.stream().forEach( line -> writer.write(line) );

The expression line -> writer.write(line) is a lambda expression implementing the functional interface Consumer, and you can't throw checked exceptions (such as IOException) inside a lambda.

The way to solve the issue is to catch the IOException inside the lambda itself, and rethrow it wrapped into a RuntimeException (which is unchecked):

this.dataContainer1.stream().forEach(line -> {
        try {
            writer.write(line); //try code that throws a checked exception
        } catch (IOException e) { //catch that checked exception
            throw new RuntimeException(e); //rethrow it as the cause of a runtime (unchecked) exception
        }
    });

At this point you don't need to catch IOException any longer (in any case, if an IOException occurs from write() you will receive it as a RuntimeException having cause that IOException.

Note: there are nicer ways to keep the type IOException without the compiler complaining, which is called Sneaky Throw. You may want to read this article about it: Sneaky throws article. Generally speaking it's not suggested to silent a checked exception like this, but sometimes there's no other choice (for example when you want to run code inside a lambda expression and you need to use a functional interface that doesn't throw, such as Consumer).

Sign up to request clarification or add additional context in comments.

16 Comments

There's UncheckedIOException right there, you should be using that instead of RuntimeException. More generally the obvious solution here is not to use forEach. Lambdas aren't transparent in 3 ways (locals, control flow, checked exceptions) - if that is annoying (and it tends to be), do not use them. Just use a while loop.
@rzwitserloot It doesn't change much, the real exception is IOException, rethrowing the one you said rather than any other runtime will behave the same in terms of flow (you dont déclare it in the signature) and if you are logging you will still see the original IOException in the logs. As for lambdas, as long as you create/use a sneaky throw construct you can keep on using lambdas without too much overhead. I agree that sometimes you can just opt for using standard loops but when these loops become complex, the stream API makes the code more readable at my taste
The variable name line suggests that actually writing lines is intended. In this case, use the right tool for the job: Files.write(filePath, dataContainer1); Files.write(filePath, dataContainer2, StandardOpenOption.APPEND); Then, no wrapping of exceptions is required. But if you are wrapping IOException in a runtime exception, using UncheckedIOException is preferable, not because of what you see in logs when the exception is not caught, but because it allows catching it and rethrowing the IOException, as the method has been declared as throws IOException
Yes, that’s what it does.
@limestreetlab you're nice to worry but we're not arguing, these are just discussions among developers :) rzwitserloot is right in many things he said, namely using streams (which is often abused by thinking it's cleaner) as well as wrapping exceptions into RuntimeException. Again, I've answered your question as is. If you ask me the solution I'd go for, is the one proposed by g00se and not the fix I proposed above (which again, is just the answer to your question and not THE answer to your problem).
|
1

Just to make it quite plain how clean Holger's solution is:

    void saveData() throws IOException {
        String filename;
        Path filePath;
        boolean condition1 = false;
        Path dirPath = Path.of("/tmp");
        filename = "missionImpossible9.csv";
        filePath = dirPath.resolve(filename);

        if (condition1) {
            Files.write(filePath, dataContainer1);
        } else {
            Files.write(filePath, dataContainer2);
        }
    }

1 Comment

Or even Files.write(filePath, condition1? dataContainer1: dataContainer2);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.