JvmSuppressWildcards: The secret sauce to your sandwich-style generics

Image for post
Image for post
Photo: amirali mirhashemian from Unsplash

If generic types are exposed in Kotlin API, consider so that your Java consumer can compile successfully.

Kotlin generics are different from (in my opinion, smarter than) Java generics. Kotlin generics has declaration-site variance and type projections, which are officially documented here. Generics is a gigantic topic itself. My usual practice is to leverage IDE warnings and compiler error messages to fix my code since they usually contain the necessary information to show you where you did wrong.

However, as we are Kotlinizing our codebase from Java to Kotlin, it is possible to have a sandwich-style code dependency that mixes Java code and Kotlin code. In such cases, when dealing with generics, your compiler and IDE may not be smart enough to help you fix those issues.

What is sandwich-style code dependency?

Image for post
Image for post
🥪 Sandwich-style Kotlin conversion

At a certain time during your Java to Kotlin conversion, there could be some Java code depending on Kotlin code, which in turn depends on Java code.

A typical example on Android is that your app code in Java depending on your library in Kotlin, which depends on Android SDK in Java.

Even if your app code is written purely in Kotlin, the sandwich might still exist: The Java-based annotation processing Dagger, depends on your app code in Kotlin, which depends on Android SDK in Java.

Now let’s look at how the secret sauce can help us by making our generic sandwich tasty.

Sandwich: List<Object> in RecyclerView methods

With the code above, the following compilation error is observed:

MyChildAdapter.java:13: error: name clash: onBindViewHolder(MyViewHolder,int,List<Object>) in MyChildAdapter overrides a method whose erasure is the same as another method, yet neither overrides the other

The error message leads us to think that we need to change our type here:

  1. Change the Kotlin generic parameter type from to so that we have the method signature It does not work.
  2. Change the bottom-level Java generic parameter type from to so that we have the method signature . It does not work either.
  3. Change the bottom-level Java generic parameter type from to . It does not work still. 🤯

Without any annotation, Kotlin translates into . The solution is to add to the Kotlin code

With , Kotlin translates the type into . is an invariant type in Java and works seamlessly with all the other Java code using the same variant.

Sandwich: Multibinding in Dagger

With the code like the above, Dagger yields the error:

MyComponent.java:8: error: [Dagger/MissingBinding] java.util.Map<java.lang.Class<? extends ViewModel>,? extends Factory> cannot be provided without an @Provides-annotated method.

Internally, Dagger is expecting the type but it is nearly impossible to infer that from the error message.

To fix this, simply add at the injection site: . This will correct the type in Java and keep the Dagger annotation processer happy.

This solution is actually tracked on Dagger issue itself: Kotlin + Dagger best practices/documentation/pain points.

Takeaway

To be compatible with Java generics, Kotlin has and to suppress or force creating wildcards. There is a section in Kotlin official documentation: Calling Kotlin from Java#Variant Generics. However, I totally forgot these when reading real-world compiler error messages. Therefore, I wrote this story to remind my future self and all Kotlin developers about the power of .

Android Developer@LinkedIn

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store