JvmSuppressWildcards: The secret sauce to your sandwich-style generics

Chao Zhang
3 min readFeb 23, 2021

--

Photo: amirali mirhashemian from Unsplash

If generic types are exposed in Kotlin API, consider @JvmSuppressWildcards 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?

🥪 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 @JvmSuppressWildcards 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 List<Any> toList<Any?> so that we have the method signatureonBindViewHolder(holder: VH, position: Int, payloads: List<Any?>) It does not work.
  2. Change the bottom-level Java generic parameter type from List<Object> to List<? extends Object> so that we have the method signature public void onBindViewHolder(MyViewHolder holder, int position, List<? extends Object> payloads) . It does not work either.
  3. Change the bottom-level Java generic parameter type from List<Object> to List<?> . It does not work still. 🤯

Without any annotation, Kotlin translates List<Any> into List<? extends Object>. The solution is to add @JvmSuppressWildcards to the Kotlin code

override fun onBindViewHolder(holder: VH, position: Int, payloads: List<@JvmSuppressWildcards Any>)

With @JvmSuppressWildcards, Kotlin translates the type List<Any>into List<Object>. List<Object>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 Map<Class<ViewModel>, Factory> but it is nearly impossible to infer that from the error message.

To fix this, simply add @JvmSuppressWildcards at the injection site: factoryMap: @JvmSuppressWildcards Map<Class<out ViewModel>, Factory> . 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 @JvmSuppressWildcards and @JvmWildcard 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 @JvmSuppressWildcards.

--

--