JDK-8180410 : ByteArrayOutputStream should not throw IOExceptions
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.io
  • Affected Version: 8,9
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2017-05-12
  • Updated: 2019-07-15
  • Resolved: 2018-03-23
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11
11 b07Fixed
Related Reports
CSR :  
Relates :  
Relates :  
Relates :  
Description
A DESCRIPTION OF THE REQUEST :
 

    ByteArrayOutputStream baos = new ByteArrayOutputStream(128);
    baos.write(123); // does NOT have a throws-exception clause
    try
    {
      baos.write("Hello world".getBytes()); // HAS a throws-exception clause
    }
    catch (IOException e)
    {
      // even though it never happens
    }

JUSTIFICATION :
It's contradictive. 
And it's easy to solve, without impact.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The ByteArrayOutputStream class should extend the OutputStream#write(byte[]) method and remove the exception clause:

    @Override
    public void write(byte b[]) // no more exception here.
    {
      try { super.write(b); } catch(IOException ioe) {}
    }
ACTUAL -
Right now, you just always have to make an empty catch-block whenever you use this write(byte[]) method.

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try
    {
      baos.write(byteArray);
    }
    catch (IOException ioe)
    {
      // never happens
    }

---------- BEGIN SOURCE ----------
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try
    {
      baos.write(byteArray);
    }
    catch (IOException ioe)
    {
      // never happens
    }
---------- END SOURCE ----------


Comments
Other options would be to add the exception-less method void writeByteArray(byte[]) {} to BAOS and int readByteArray(byte[]) {} to BAIS.
30-11-2017

This issue comes up every few years and the concern has always been the source compatibility issues that Stuart listed in a recent comment. If we were to change write(byte[]) now then it would be painful transition for those that are compiling to multiple releases without specifying --release (or -source/-target). Adding a new exception-less writeBytes(byte[]) seems worth it, esp for usages where the scope of the BAOS is local and the code writes to the BAOS before getting the bytes with toByteArray().
26-05-2017

(I had previously said this was a binary compatibility issue. That's incorrect; I think it's only a source compatibility issue.) I see two source incompatibilities. 1) subclasses class MyBaos extends ByteArrayOutputStream { public void write(byte[] ba) throws IOException { super.write(ba); } } If ByteArrayOutputStream were to add an override that isn't declared to throw IOException, this code will break. In the quick search I did earlier, many subclasses don't override the methods in question and so are unaffected. Those that do override will either a) not declare IOException, so they won't be broken, or b) do declare IOException but they don't actually do anything that throws IOException. Cases b) will be broken but the fix is simply to remove the 'throws IOException' declaration. 2) calls within try/catch Consider the following code: void writeWithTryCatch(byte[] ba) { try { baos.write(ba); } catch (IOException ioe) { } } If ByteArrayOutputStream were to add an override that isn't declared to throw IOException, this code will also break, since it's illegal to have a catch for a checked exception that isn't thrown from the body of the try. The fix here is simply to remove that catch clause, or possibly the entire try/catch wrapper. ========== The "catch" with both of these mitigations is that it's difficult to have source code that compiles on both releases. For calls within try/catch, the try/catch block would have to be left there. A dummy call to a utility method that is declared to throw IOException, but otherwise does nothing, can be added. This satisfies the requirement that the code in the try-block is declared to throw an exception that's caught by a catch block. For the subclass case, it's a bit stickier. It's legal for an overriding method to be declared not to throw IOException. If it calls super.write(ba), that *might* be declared to throw IOException, which needs to be handled. This call would have to be enclosed in a try/catch of IOException. This would work, unless super.write(ba) *doesn't* throw IOException. This could be mitigated with a dummy call to a utility method, as mentioned above. Bottom line is that it can be a bit painful to deal with the source compatibilities, but only if there is source code that needs to be compiled on both old and new versions. I think this should be fairly rare. The benefit is that new code needn't deal with unnecessary IOExceptions, and code migrating forward can be cleaned up.
25-05-2017

Some comments: It's really a shame we can't fix this due to checked exception rules. (Binary compatibility is just fine.) This is, in part, an EOU problem which can be fixed by a new API point. class BAOS { /** @since 10 */ final void writeAll(byte[] a) { write(a, 0, a.length); } } We might have an annotation (and exception loophole, a JLS change) for this kind of compatibility problem, so we can salvage such API points: class BAOS { /** @since 1.0 */ @DeprecatedException(java.io.IOException) public void write(byte[] a) // removed in 10: throws IOException { ... } } try { mybaos.write(mya); } catch (IOException ex) { //error: exception IOException is never thrown in body of corresponding try statement throw new InternalError(); } The annotation should change the error into a warning.
25-05-2017

John, in our tech meeting today I suggested exactly that as well: adding a new such method.
25-05-2017

It might be reasonable to fix this. The write(byte[]) and flush() methods could be overridden and declared not to throw IOException. This does raise a compatibility issue, as was noted in JDK-4787155 which was closed because of the compatibility risk to subclasses. I did a quick search on grepcode and found that, while there are many cases where ByteArrayOutputStream is subclassed, there are few cases where write(byte[]) or flush() are overridden. The most common case is simply to expose the protected members buf and count. I did see one case where flush() was overridden, and it did NOT declare "throws IOException". So while there is a compatibility risk, it does seem minor. Similar analysis should be done on ByteArrayInputStream.
25-05-2017