Search code examples
javabytearrayoutputstream

Accept only part of message written to ByteArrayOutputStream object


I'm trying to get raw xml of SOAPMessage object and I'm doing that using ByteArrayOutputStream class, but its losing readability once there's compressed attachment added to the message object.

Here's my code:

ByteArrayOutputStream out = new ByteArrayOutputStream();
message.writeTo(out);
strMsg = new String(out.toByteArray());

where message is SOAPMessage object. The problem is, when I print it to the stdout or logs, or some other output, strMsg might be huge and contain byte data of compressed attachment. Is there a way to truncate it at the moment of writing message to ByteArrayOutputStream object? I know, that I can easily truncate strMsg, but I don't see a reason to collect all the data inside ByteArrayOutputStream.


Solution

  • I know, that I can easily truncate strMsg, but I don't see a reason to collect all the data inside ByteArrayOutputStream.

    The problem is that you only have the writeTo method to work with, and that is designed to output the entire message.

    You could create a custom variant of ByteArrayOutputStream that will only accept a specified number of bytes. When if writeTo exceeds the limit, your class needs to throw an IO exception.

    The practical problem is that creating and throwing an exception is expensive. This is the real basis for the mantra "Only use exceptions in exceptional case" - https://softwareengineering.stackexchange.com/questions/184654/ive-been-told-that-exceptions-should-only-be-used-in-exceptional-cases-how-do. If you are logging lots of SOAP messages, that could be a problem.

    The main performance bottleneck for exception creation / throwing is actually the creation of the exception object: specifically the part where the Throwable constructor calls fillInStacktrace to capture the stack frames. There are a couple of ways to ameliorate this:

    • Instead of new-ing an exception object each time, you can create one instance, cache it in a "global" and reuse it multiple times.

    • If the exception is a custom one, you can override its fillInStacktrace method to do nothing.

    In both cases, you end up with exceptions that will give you incorrect results if you try to print the stacktraces. But if you are using exceptions to implement a "non-exceptional" code path ('cos you have no real alternative!) then that's probably not relevant.


    There is a variant of my solution above where custom byte sink class silently throws away bytes instead of throwing an exception. That avoids the cost of exceptions (but see above), but it does not avoid the unnecessary work that writeOut is doing when serializing a large SOAP message to a byte sink that has "stopped listening".


    The other thing to note is truncating after a certain number of bytes is liable to break if you are using a multi-byte character encoding such as UTF-8. Consider what happens if you truncate at 100 bytes, and the 100th byte is in the middle of a character. Note that the behavior of new String(byte[]) is unspecified if byte[] contains sequences that are not valid characters in the default characterset.