Java InputStream + BufferedInputStream swallows exceptions
Asked Answered
T

0

6

I have a problem where my program is not ending as expected when an exception is thrown. I have tracked it down to the combination of InputStream and BufferedInputStream swallowing the exceptions. The following code demonstrates the problem:

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Test {
    public static void main(String[] args) throws IOException {
        try (InputStream stream = new BufferedInputStream(new MyStream())) {
            int x = stream.read();
            while (x != -1) {
                System.out.println(x);
                x = stream.read();
            }
        }
    }

    static class MyStream extends InputStream {
        int i = 0;

        @Override
        public int read() throws IOException {
            i = i + 1;
            if (i == 5) {
                throw new IOException("i == 5");
            }
            if (i > 10) {
                return -1;
            }
            return i;
        }
    }
}

This produces the output 1 2 3 4 6 7 8 9 10

If the BufferedInputStream is removed, the exception is thrown when i == 5 as expected.

I tracked it down to the InputStream code. If multiple bytes are read, the InputStream will only propagate an IOException for the first byte, not subsequent bytes. BufferedInputStream changes the read behavior to read multiple bytes at a time and so the IOException is caught by InputStream and discarded.

What is the correct behavior here?

Is it a rule that "Once an IOException, always an IOException" i.e. MyStream needs to remember that it threw an IOException and throw again on any subsequent call?

Other alternatives:

  • throw an unchecked exception that will not be caught by the InputStream. I don't think I can throw a different checked exception while extending InputStream
  • also override the multi byte read method so it does not catch the IOException. However this seems like it would be vulnerable to the same problem if it was wrapped again by other input streams.

I am grateful for any guidance. At the moment, I think the unchecked exception is my best alternative.

Tureen answered 29/4, 2021 at 0:48 Comment(8)
If multiple bytes are read, the InputStream will only propagate an IOException for the first byte, Do you mean "BufferedInputStream"? Otherwise I'm a little confused what your actual test result was.Lactescent
@markspaceNo, InputStream is correct, see the description: docs.oracle.com/javase/8/docs/api/java/io/…. The BufferedInputStream calls that method.Scarabaeoid
So @Scarabaeoid is correct, that's the documented behavior of InputStream or at least its default implementation. I'll give you eating I/O errors like that is weird, but that's what it does. The docs give you a hint that you can subclass InputStream and provide your own, better implementation if needs be.Lactescent
And now that I think about it, that's all I've ever used the actual class InputStream for is to subclass it. All other input streams are subclasses: FileInputStream, SocketInputStream, ObjectInputStream etc. It's a base class designed to be replaced.Lactescent
If I subclass it with a better implementation, someone can still wrap another InputStream around it that will again swallow the exceptions. I can see that this is the documented behavior, I'm just not sure of the best way to work with it when implementing an InputStream.Tureen
You already subclassed it with MyStream, you just need to override that specifc method as well. And if you don't want your class to get subclassed again, then mark it as final.Scarabaeoid
Preventing subclassing doesn't help. If someone wraps another stream around it e.g. new BufferedInputStream(new SomeOtherStream(new MyStream())) I have the same problem, if SomeOtherStream only overrides read() and relies on InputStream for the rest of the implementation.Tureen
I am coming to the conclusion that the correct answer is "once an exception, always an exception". I think the reason InputStream swallows the exception is that if you have already read some bytes, it is preferable to get those bytes than the exception - you can't have both. I will save the exception and throw it again if read() is called again.Tureen

© 2022 - 2024 — McMap. All rights reserved.