If a serializable object contains a field of type Foo, this implies that all subtypes of Foo can be transmitted, whether they were designed with serialization in mind or not. This is especially bad for commonly used base types such as List or Exception. At the limit, if you have a field of type Object or Any, there's no choice but to use an explicit whitelist.
Contrast with how Go does unmarshaling in its standard library (which works with structs and arrays but not interfaces), and functional languages (which use unions in the form of algebraic data types, not inheritance). These are closed-world type systems where we can generate the whitelist by walking the type tree from the base type.
The algebraic approach, in this context, is similar to a superclass naming its subclasses (rather than a subclass naming its superclasses as java does). I'm not sure, but I suspect this open-world choice is deliberate, allowing extensibility. Java is very attentive to security considerations elsewhere. It also would be a too-dramatic change for mainstream OO programming languages.
A third approach is explicitly closed-world: naming the set of classes permitted for deserialization, as per some other commenters. e.g. JAXB does this by necessity, for binding to XML Schema's algebraic type system.
Or simply having ObjectInputStream execute readResolve/readObject(ObjectInputStream) under a different security context. No need for whitelisting.
Well, yes and no. Java's open-world type system is one of its greatest features. To counteract any security holes this may cause, Java has a very strong security model, where each piece of executable code is associated with a security context based on its origin, and what it is allowed to do is restricted by a security policy based on the code's domain. In this case, however, no foreign code was directly introduced. It was found to be possible to exploit code already found within the application's safe domain (a third-party library).
The Java solution is simple: during deserialization of external data, treat execute custom deserialization code as if it was foreign, and execute it within a foreign security context. It's best if this was done directly in the JDK code, and I assume a patch will adress this.
But, in an application that deserializes objects from an untrusted source, the fact that they happen to be on the class path leads to them being available to use in an undesirable manner.
It would have been nice if ObjectInputStream was abstract and required a subclass that provided a whitelist of classes.