Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8180410

ByteArrayOutputStream should not throw IOExceptions

    Details

      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 ----------

        Issue Links

          Activity

          Hide
          smarks Stuart Marks added a comment - - edited
          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.
          Show
          smarks Stuart Marks added a comment - - edited 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.
          Hide
          jrose John Rose added a comment - - edited
          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.
          Show
          jrose John Rose added a comment - - edited 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.
          Hide
          bpb Brian Burkhalter added a comment -
          John, in our tech meeting today I suggested exactly that as well: adding a new such method.
          Show
          bpb Brian Burkhalter added a comment - John, in our tech meeting today I suggested exactly that as well: adding a new such method.
          Hide
          smarks Stuart Marks added a comment -
          (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.



          Show
          smarks Stuart Marks added a comment - (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.
          Hide
          alanb Alan Bateman added a comment -
          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().
          Show
          alanb Alan Bateman added a comment - 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().
          Hide
          bpb Brian Burkhalter added a comment - - edited
          Other options would be to add the exception-less method void writeByteArray(byte[]) {} to BAOS and int readByteArray(byte[]) {} to BAIS.
          Show
          bpb Brian Burkhalter added a comment - - edited Other options would be to add the exception-less method void writeByteArray(byte[]) {} to BAOS and int readByteArray(byte[]) {} to BAIS.

            People

            • Assignee:
              bpb Brian Burkhalter
              Reporter:
              webbuggrp Webbug Group
            • Votes:
              0 Vote for this issue
              Watchers:
              7 Start watching this issue

              Dates

              • Created:
                Updated: