The unfortunate stance on ADTs is that Java doesn't want them generalized. The standard answer is use Records and Sealed classes.
What does this mean? Well with generics we can have two libraries that know about Map<> and concrete types TypeA and TypeB and can interface type-safely using Map<TypeA, TypeB>.
What we can't do is have two libraries that know about TypeA and TypeB interface type-safely using a sealed type of TypeA and TypeB. Even without considering libraries, another use we may want to talk about a sealed type of TypeA and TypeC, but of course TypeA can only be in one of the sealed class trees.
The worst part is that Java already has a Union type that's only ever used in multi catch(ExceptionA|ExceptionB e).