What you will build
A compressed attachment can be small while its expanded data is much larger. Apply an expanded-byte limit while reading, before handing the result to a parser or application. This Java example accepts a compressed byte array and returns expanded bytes only after the stream reaches its end successfully.
The fixture compressor is part of the sample, so every test is reproducible without downloading an archive. Tests include a valid exact-limit payload, oversized content, a truncated trailer, a corrupted checksum and an empty payload. These exercise the actual JDK decompressor.
Prerequisites
JDK 21, including javac and java. The example uses java.base only. It reads small in-memory archives and creates no files or network connections. Save as BoundedGzip.java.
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.zip.*;
public class BoundedGzip {
static byte[] unpack(byte[] compressed, int limit) throws IOException {
if (limit < 0) throw new IllegalArgumentException("negative limit");
try (var in = new GZIPInputStream(new ByteArrayInputStream(compressed));
var out = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int total = 0;
int n;
while ((n = in.read(buffer)) != -1) {
if (n > limit - total) throw new IOException("expanded size limit");
out.write(buffer, 0, n);
total += n;
}
return out.toByteArray();
}
}
static byte[] pack(String value) throws IOException {
var out = new ByteArrayOutputStream();
try (var gzip = new GZIPOutputStream(out)) {
gzip.write(value.getBytes(StandardCharsets.UTF_8));
}
return out.toByteArray();
}
static void reject(byte[] bytes, int limit) throws IOException {
try { unpack(bytes, limit); }
catch (IOException expected) { return; }
throw new AssertionError("accepted invalid or oversized gzip");
}
public static void main(String[] args) throws Exception {
byte[] zipped = pack("hello");
if (!Arrays.equals(unpack(zipped, 5), "hello".getBytes(StandardCharsets.UTF_8)))
throw new AssertionError("round trip");
reject(zipped, 4);
reject(pack("x".repeat(10000)), 100);
reject(Arrays.copyOf(zipped, zipped.length - 3), 20);
byte[] corrupt = zipped.clone();
corrupt[corrupt.length - 8] ^= 1;
reject(corrupt, 20);
if (unpack(pack(""), 0).length != 0) throw new AssertionError("empty");
System.out.println("hello; exact limit, oversize, truncation, CRC, empty passed");
}
}
Run and expected output
javac BoundedGzip.java
java BoundedGziphello; exact limit, oversize, truncation, CRC, empty passedHow it works
GZIPInputStream supplies format decoding and reports corrupt input through I/O exceptions. The application supplies the resource policy. Each read fills a fixed 1,024-byte buffer. Before writing those bytes to the accumulating output, the code compares the chunk size with the remaining allowance.
Using n > limit - total avoids adding two potentially large integers just to discover an overrun. The invariant is that total never exceeds limit. A chunk that crosses the limit causes an exception before any of that chunk is added. Exactly the allowed number of bytes is accepted, but the function still reads until end-of-stream so a bad trailer cannot masquerade as success.
Only after successful completion does the function create the returned array. Try-with-resources closes both streams during success and failure. The rejection helper requires an IOException; if invalid input is accepted, an AssertionError makes the process fail.
Edge cases and scope
The limit counts bytes, not characters. Decode UTF-8 and apply text-specific rules afterward if needed. A zero limit accepts an empty gzip member but rejects expanded content. A negative limit is a programming error. Corrupting the checksum trailer confirms that integrity checking is reached on an otherwise small payload.
This cap bounds accepted output, not complete process memory. The input array, stream buffers, growing output and returned copy all consume memory. Set limits conservatively. For uploads, also bound compressed input size and request duration; this function does not enforce network timeouts or CPU isolation.
Troubleshooting
If valid data fails at the boundary, verify its size in bytes using the actual encoding. If truncation appears successful, check that the loop did not return as soon as the cap was reached. Never catch IOException and return a partial array as complete. Run javac from the file's directory and use the exact public class name when invoking java.
Primary references
Continue with Java tutorials and all Java Samples categories.