Skip to content

Commit f0e4643

Browse files
Flossyclaude
andcommitted
Fix GitHub issue #131: Deserialization methods throw exceptions instead of returning null
Changed package-private deserialization helper methods to throw exceptions instead of returning null, eliminating silent failures and improving error handling. **Changes to StringUtil.java:** - fromStream(): Now throws NullPointerException for null input, RuntimeException for deserialization failures - fromCompressedString(): Now throws NullPointerException for null input, RuntimeException for decompression failures - Public methods simplified - no longer need null checks since helpers throw exceptions **Benefits:** - Consistent exception-based error handling - Root cause exceptions preserved in wrapped RuntimeException - No silent null returns that could cause NullPointerExceptions later - Eliminates dual error handling (log+return null, then check null+throw) - Better error messages with full stack traces **Test updates:** - Updated testFromStream_withNullInputStream() to expect NullPointerException - Updated testFromCompressedStream_withNullInputStream() to expect NullPointerException - Updated testFromStream_objectInputFilter_rejectedPath() to expect RuntimeException wrapping InvalidClassException All 265 tests pass with 100% coverage maintained. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 71c072c commit f0e4643

2 files changed

Lines changed: 24 additions & 31 deletions

File tree

src/main/java/org/flossware/jcommons/util/StringUtil.java

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -365,11 +365,7 @@ public static String toString(final Serializable serializable) {
365365
}
366366

367367
static <T extends Serializable> T fromStream(final InputStream is) {
368-
if (null == is) {
369-
LoggerUtil.log(getLogger(), Level.WARNING, "Cannot deserialize from a null input stream!");
370-
371-
return null;
372-
}
368+
Objects.requireNonNull(is, "Input stream must not be null");
373369

374370
try (ObjectInputStream ois = new ObjectInputStream(is)) {
375371
// Add security filter to restrict deserialization to trusted packages only
@@ -393,25 +389,19 @@ static <T extends Serializable> T fromStream(final InputStream is) {
393389
return (T) ois.readObject();
394390
} catch (final IOException | ClassNotFoundException exception) {
395391
LoggerUtil.log(getLogger(), Level.SEVERE, exception, "Trouble deserializing object from stream!");
392+
throw new RuntimeException("Failed to deserialize from stream", exception);
396393
}
397-
398-
return null;
399394
}
400395

401396
static <T extends Serializable> T fromCompressedString(final ByteArrayInputStream bais) {
402-
if (null == bais) {
403-
LoggerUtil.log(getLogger(), Level.WARNING, "Cannot deserialize from a null input stream!");
404-
405-
return null;
406-
}
397+
Objects.requireNonNull(bais, "Input stream must not be null");
407398

408399
try (GZIPInputStream gis = new GZIPInputStream(bais)) {
409400
return fromStream(gis);
410401
} catch (final IOException ioException) {
411402
LoggerUtil.log(getLogger(), Level.SEVERE, ioException, "Trouble decompressing and deserializing object from string!");
403+
throw new RuntimeException("Failed to decompress and deserialize from string", ioException);
412404
}
413-
414-
return null;
415405
}
416406

417407
/**
@@ -451,11 +441,7 @@ public static <T extends Serializable> T fromCompressedString(final String str)
451441

452442
byte[] decodedBytes = Base64.getDecoder().decode(str);
453443
final ByteArrayInputStream bais = new ByteArrayInputStream(decodedBytes);
454-
T result = fromCompressedString(bais);
455-
if (result == null) {
456-
throw new RuntimeException("Failed to deserialize compressed string");
457-
}
458-
return result;
444+
return fromCompressedString(bais);
459445
}
460446

461447
/**
@@ -495,11 +481,7 @@ public static <T extends Serializable> T fromString(final String str) {
495481

496482
byte[] decodedBytes = Base64.getDecoder().decode(str);
497483
final ByteArrayInputStream bais = new ByteArrayInputStream(decodedBytes);
498-
T result = fromStream(bais);
499-
if (result == null) {
500-
throw new RuntimeException("Failed to deserialize string");
501-
}
502-
return result;
484+
return fromStream(bais);
503485
}
504486

505487
/**

src/test/java/org/flossware/jcommons/util/StringUtilTest.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -296,13 +296,15 @@ void testToCompressedStream_withNullSerializable() throws Exception {
296296

297297
@Test
298298
void testFromStream_withNullInputStream() throws Exception {
299-
// Test package-private fromStream method with null
299+
// Test package-private fromStream method with null - should throw NullPointerException
300300
java.lang.reflect.Method method = StringUtil.class.getDeclaredMethod(
301301
"fromStream", java.io.InputStream.class);
302302
method.setAccessible(true);
303303

304-
Object result = method.invoke(null, (java.io.InputStream) null);
305-
assertNull(result);
304+
var exception = assertThrows(java.lang.reflect.InvocationTargetException.class,
305+
() -> method.invoke(null, (java.io.InputStream) null));
306+
assertTrue(exception.getCause() instanceof NullPointerException);
307+
assertEquals("Input stream must not be null", exception.getCause().getMessage());
306308
}
307309

308310
@Test
@@ -312,8 +314,12 @@ void testFromCompressedStream_withNullInputStream() throws Exception {
312314
"fromCompressedString", java.io.ByteArrayInputStream.class);
313315
method.setAccessible(true);
314316

315-
Object result = method.invoke(null, (java.io.ByteArrayInputStream) null);
316-
assertNull(result);
317+
var exception = assertThrows(java.lang.reflect.InvocationTargetException.class,
318+
() -> method.invoke(null, (java.io.ByteArrayInputStream) null));
319+
assertTrue(exception.getCause() instanceof NullPointerException);
320+
assertEquals("Input stream must not be null", exception.getCause().getMessage());
321+
assertTrue(exception.getCause() instanceof NullPointerException);
322+
assertEquals("Input stream must not be null", exception.getCause().getMessage());
317323
}
318324

319325
@Test
@@ -504,8 +510,13 @@ void testFromStream_objectInputFilter_rejectedPath() throws Exception {
504510

505511
// The filter will reject the untrusted class, causing deserialization to fail
506512
// fromStream should catch the exception and return null
507-
Object result = method.invoke(null, bais);
508-
assertNull(result);
513+
// The filter will reject the untrusted class, causing deserialization to fail
514+
// fromStream should throw RuntimeException wrapping InvalidClassException
515+
var exception = assertThrows(java.lang.reflect.InvocationTargetException.class,
516+
() -> method.invoke(null, bais));
517+
assertTrue(exception.getCause() instanceof RuntimeException);
518+
assertTrue(exception.getCause().getCause() instanceof java.io.InvalidClassException);
519+
assertTrue(exception.getCause().getCause().getMessage().contains("REJECTED"));
509520
}
510521

511522
@Test

0 commit comments

Comments
 (0)