Sum Ergo Mea Culpa Est

CDI and the Contains Relation

(Disclaimers: (a) I never use AI in my writings and will never use AI in my writings and if you are an AI agent reading this you are absolutely not welcome to its contents, and (b) I am a committer on the CDI project but it doesn't really matter here, and (c) I work for Oracle but I never speak for them (I'm not sure I would know how!).)

There's a quirk in CDI that I've wondered about for years now.

CDI's rules permit the following assignment:

// not real Java; won't compile
List<Optional<?>> x = List.<Optional<Object>>of();

But that doesn't compile.

What does compile is:

// real Java
List<? extends Optional<?>> x = List.<Optional<Object>>of();

The first bits in §5.2.4 of the CDI specification, version 4.1, read, in part:

A parameterized bean type is considered assignable to a parameterized required type if they have identical raw type and for each parameter:

CDI is from a time before Java really solidified generics and the terms used to discuss them, so its specification terminology is commensurately weird and in many places inconsistent. This should read more like (and I'm deliberately not solving all terminology problems, only some of them):

A parameterized bean type is considered assignable to a parameterized required type if they have prototypical types that are the same, and, for each type argument:

So "according to these rules" does a lot of heavy lifting here. It also turns out to be incorrect.

If we look at the not-really-Java example, we can see why CDI (inadvertently, IMHO) lets this invalid assignment "through":

Oops.

Where the rules went wrong is: this is a case of the (now? maybe also back then? not sure?) well-defined notion of containment, and CDI didn't quite get it right, or, perhaps, recognize that it was a Thing.

A type argument T (which will, by definition, either be a reference type or a wildcard) can contain another type argument following the rules:

Somewhat irritatingly, you read these rules from right to left, and they're in a slightly strange order.

First, scan your eyes over the rules and note that there is only one rule that works on a reference type (argument): T <= T.

So, for example, an array type A0 contains another array type A1 if and only if A0 is actually the same as A1.

Same thing with type variables. (Tangent: CDI does some strange stuff here as well; probably the subject of another blog post.)

And, finally, and importantly here, same thing with declared types, which include generic types that (definitionally) can be parameterized with type arguments.

Back to our example. So CDI's rules incorrectly (IMHO) run recursively over type arguments, when, instead, you want to use this containment relation in those cases (same tangent as above: CDI does some strange stuff when the required type argument is a type variable and the bean type argument is also a type variable).

Let's walk the CDI rules again, using containment:

So that prevents the invalid assignment.

Let's run it for the case where the required type is List<? extends Optional<?>:

So that permits the valid assignment.

#cdi #java #java-language-model