Serguey Asael Shinder: ObjectInputFilter's maxarray limits stream arrays, not what readObject allocates
The JDK has had deserialization filters since Java 9, and their pattern syntax includes limits that look like memory protection:
jdk.serialFilter=maxarray=100000;maxbytes=1048576;maxdepth=20
The javadoc of ObjectInputFilter.Config defines them: maxdepth is "the maximum depth of a graph", maxrefs "the maximum number of internal references", maxbytes "the maximum number of bytes in the input stream", and maxarray "the maximum array length allowed".
It is easy to read maxarray as "no array bigger than this will be created during deserialization". It does not say that, and it cannot mean it.
What the filter actually sees. A filter is called with an ObjectInputFilter.FilterInfo, and its arrayLength() is "the number of array elements when deserializing an array of the class". That is an array written into the stream as an array. If a class instead writes an int and its readObject later does new Object[count], the stream holds an integer, not an array, and the filter has nothing to measure.

Where that bites. This is the shape of the issue fixed in Guava 33.7.2. According to the advisory, CompactHashMap, CompactHashSet and MapMakerInternalMap read a count from the stream and allocated arrays of that size themselves. A maxarray limit would have let those payloads through, and so would maxbytes, because the stream was tiny.
What does work. The part of the filter that decides by class. The ObjectInputFilter javadoc advises that filters "should be designed for the specific use case and expected types", and that in many cases "any class not ALLOWED by the filter should be REJECTED". An allow-list of the few classes a stream is supposed to contain, ending in !*, rejects Guava's internal collections, and every other gadget, before their readObject runs.
jdk.serialFilter=com.example.dto.*;java.lang.*;java.util.ArrayList;!*
I keep the numeric limits too, they are cheap and they do stop a stream that nests or references its way into trouble. But I treat the class list as the control and the limits as a backstop, because a limit can only check what passes through the filter, and the dangerous allocation often happens after it.
The code in this note is written to compile as shown; the behaviour described is the one stated in the linked javadoc for Java SE 25.