From 792947fc0552a984da52e2c6f42d61311361a6c8 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Fri, 3 Oct 2025 20:40:43 +0200 Subject: [PATCH] Fix inconsistent exception in `IOUtils.toByteArray` The implementation of `IOUtils.toByteArray(InputStream, int, int)` added in #776 throws different exceptions depending on the requested size: * For request sizes larger than the internal chunk size, it correctly throws an `EOFException`. * For smaller requests, it incorrectly throws a generic `IOException`. This PR makes the behavior consistent by always throwing an `EOFException` when the stream ends prematurely. Note: This also affects `RandomAccessFiles.read`. Its previous truncation behavior was undocumented and inconsistent with `RandomAccessFile.read` (which reads as much as possible). The new behavior is not explicitly documented here either, since it is unclear whether throwing on truncation is actually desirable. --- src/main/java/org/apache/commons/io/IOUtils.java | 3 ++- src/test/java/org/apache/commons/io/IOUtilsTest.java | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/apache/commons/io/IOUtils.java b/src/main/java/org/apache/commons/io/IOUtils.java index a882d4d87cb..e157d51a5a9 100644 --- a/src/main/java/org/apache/commons/io/IOUtils.java +++ b/src/main/java/org/apache/commons/io/IOUtils.java @@ -2957,6 +2957,7 @@ public static byte[] toByteArray(final InputStream input, final long size) throw * @param input the input to read, not null. * @param size the size of the input to read, where 0 < {@code size} <= length of input. * @return byte [] of length {@code size}. + * @throws EOFException if the end of the input is reached before reading {@code size} bytes. * @throws IOException if an I/O error occurs or input length is smaller than parameter {@code size}. * @throws IllegalArgumentException if {@code size} is less than zero. */ @@ -2974,7 +2975,7 @@ static byte[] toByteArray(final IOTriFunction offset += read; } if (offset != size) { - throw new IOException("Unexpected read size, current: " + offset + ", expected: " + size); + throw new EOFException("Unexpected read size, current: " + offset + ", expected: " + size); } return data; } diff --git a/src/test/java/org/apache/commons/io/IOUtilsTest.java b/src/test/java/org/apache/commons/io/IOUtilsTest.java index 02574946bf8..df05c648f8d 100644 --- a/src/test/java/org/apache/commons/io/IOUtilsTest.java +++ b/src/test/java/org/apache/commons/io/IOUtilsTest.java @@ -158,7 +158,9 @@ static Stream testToByteArray_InputStream_Size_BufferSize_Throws() { Arguments.of(-1, 128, IllegalArgumentException.class), // Invalid buffer size Arguments.of(0, 0, IllegalArgumentException.class), - // Huge size: should not cause OutOfMemoryError + // Truncation with requested size < chunk size + Arguments.of(64, 128, EOFException.class), + // Truncation with requested size > chunk size Arguments.of(Integer.MAX_VALUE, 128, EOFException.class)); } @@ -1757,6 +1759,13 @@ void testToByteArray_InputStream_Size() throws Exception { } } + @Test + void testToByteArray_InputStream_Size_Truncated() throws Exception { + try (InputStream in = new NullInputStream(0)) { + assertThrows(EOFException.class, () -> IOUtils.toByteArray(in, 1), "Should have failed with EOFException"); + } + } + @ParameterizedTest @MethodSource void testToByteArray_InputStream_Size_BufferSize_Succeeds(final byte[] data, final int size, final int bufferSize) throws IOException {