Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash with @Body and JacksonConverterFactory: java.lang.NoSuchMethodError: No virtual method getAnnotationsByType #3071

Closed
1 task done
TTransmit opened this issue Apr 10, 2019 · 2 comments

Comments

@TTransmit
Copy link

TTransmit commented Apr 10, 2019

What kind of issue is this?

I've tried and failed to create a bug using the retrofit bug template. It runs fine with MockWebServer but not in an actual Android app.

It runs fine in Android with GsonConverterFactory instead of JacksonConverterFactory.

It's also runs fine if the @Body is changed to a String instead of the Kotlin data class.

I tried with Retrofit 2.5.0, 2.4.0 and 2.2.0 and all had the same issue.

The crash on Android gives the following incomplete stack trace in logcat:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.retrofitbodycrash, PID: 19561
    java.lang.NoSuchMethodError: No virtual method getAnnotationsByType(Ljava/lang/Class;)[Ljava/lang/annotation/Annotation; in class Ljava/lang/reflect/Method; or its super classes (declaration of 'java.lang.reflect.Method' appears in /system/framework/core-libart.jar)
        at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector.isRequiredByAnnotation(KotlinAnnotationIntrospector.kt:61)
        at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector.hasRequiredMarker(KotlinAnnotationIntrospector.kt:68)
        at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector.access$hasRequiredMarker(KotlinAnnotationIntrospector.kt:23)
        at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector$hasRequiredMarker$1.invoke(KotlinAnnotationIntrospector.kt:33)
        at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector$hasRequiredMarker$1.invoke(KotlinAnnotationIntrospector.kt:23)
        at com.fasterxml.jackson.module.kotlin.ReflectionCache.javaMemberIsRequired(KotlinModule.kt:92)
        at com.fasterxml.jackson.module.kotlin.KotlinAnnotationIntrospector.hasRequiredMarker(KotlinAnnotationIntrospector.kt:26)
        at com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair.hasRequiredMarker(AnnotationIntrospectorPair.java:307)
        at com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair.hasRequiredMarker(AnnotationIntrospectorPair.java:307)
        at com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$4.withMember(POJOPropertyBuilder.java:655)
        at com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder$4.withMember(POJOPropertyBuilder.java:652)
        at com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder.fromMemberAnnotations(POJOPropertyBuilder.java:1143)
        at com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder._findRequired(POJOPropertyBuilder.java:652)
        at com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder.getMetadata(POJOPropertyBuilder.java:220)
        at com.fasterxml.jackson.databind.ser.BeanSerializerFactory._constructWriter(BeanSerializerFactory.java:771)
        at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.findBeanProperties(BeanSerializerFactory.java:583)
        at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.constructBeanSerializer(BeanSerializerFactory.java:368)
        at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.findBeanSerializer(BeanSerializerFactory.java:279)
        at com.fasterxml.jackson.databind.ser.BeanSerializerFactory._createSerializer2(BeanSerializerFactory.java:231)
        at com.fasterxml.jackson.databind.ser.BeanSerializerFactory.createSerializer(BeanSerializerFactory.java:165)
        at com.fasterxml.jackson.databind.SerializerProvider._createUntypedSerializer(SerializerProvider.java:1388)
        at com.fasterxml.jackson.databind.SerializerProvider._createAndCacheUntypedSerializer(SerializerProvider.java:1356)
        at com.fasterxml.jackson.databind.SerializerProvider.findValueSerializer(SerializerProvider.java:551)
        at com.fasterxml.jackson.databind.SerializerProvider.findTypedValueSerializer(SerializerProvider.java:758)
        at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.forRootType(ObjectWriter.java:1359)
        at com.fasterxml.jackson.databind.ObjectWriter.<init>(ObjectWriter.java:112)
        at com.fasterxml.jackson.databind.ObjectMapper._newWriter(ObjectMapper.java:689)
        at com.fasterxml.jackson.databind.ObjectMapper.writerFor(ObjectMapper.java:3351)
        at retrofit2.converter.jackson.JacksonConverterFactory.requestBodyConverter(JacksonConverterFactory.java:68)
        at retrofit2.Retrofit.nextRequestBodyConverter(Retrofit.java:280)
        at retrofit2.Retrofit.requestBodyConverter(Retrofit.java:260)
        at retrofit2.RequestFactory$Builder.parseParameterAnnotation(RequestFactory.java:706)
        at retrofit2.RequestFactory$Builder.parseParameter(RequestFactory.java:295)
        at retrofit2.RequestFactory$Builder.build(RequestFactory.java:182)
        at retrofit2.RequestFactory.parseAnnotations(RequestFactory.java:65)
    	at retrofit2.ServiceMethod.parseAnnotations(ServiceMeth

The Code causing the crash. MainActivity modified in a new Android Kotlin AndroidX project:

package com.example.retrofitbodycrash

import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
import okhttp3.HttpUrl
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.Result
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.jackson.JacksonConverterFactory
import retrofit2.http.Body
import retrofit2.http.POST

class RetrofitBug {
    @Rule
    @JvmField
    val server = MockWebServer()

    internal interface Service {
        @POST("/path")
        fun search(@Body keyword: SignUpRequest): Single<Result<SignUpResponse>>
    }

    @Test
    fun test() {
        val example = createRetrofitService(Service::class.java, server.url("/"))

        server.enqueue(
            MockResponse()
                .addHeader("Content-Type", "application/json")
                .setBody("{\"first_name\": \"Mr\", \"last_name\": \"Test\", \"email\": \"[email protected]\", \"id\": 1}")
        )

        var response: SignUpResponse? = null

        val disposable = getRxNetworkResult(example.search(
            SignUpRequest(
                first_name = "Mr",
                last_name = "Test",
                email = "[email protected]",
                password = "password"
            )))
            .subscribeOn(Schedulers.trampoline())
            .observeOn(Schedulers.trampoline())
            .subscribe({
                response = it
                System.out.println("Success")
            }, {
                System.out.println("Error")
            })

        assertEquals("Mr", response?.first_name)

        val recordedRequest = server.takeRequest()
        assertEquals("/path", recordedRequest.getPath())
    }

    fun <T> createRetrofitService(
        service: Class<T>,
        baseUrl: HttpUrl? = HttpUrl.parse("https://fakeurl.test")
    ): T =
        Retrofit.Builder()
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .addConverterFactory(JacksonConverterFactory.create(ignoreUnknownMapper()))
            .baseUrl(baseUrl)
            .build()
            .create(service)

    fun <T> getRxNetworkResult(single: Single<Result<T>>): Single<T> = single.map {
        when {
            it.isError -> throw NetworkError(
                it.error() ?: Throwable("Unknown NetworkError")
            )

            it.response()?.isSuccessful == false -> throw ApiError(
                it.response()?.code() ?: 500,
                it.response()?.errorBody()?.string() ?: "Unknown ApiError"
            )

            else -> it.response()?.body() ?: throw ApiError(
                500,
                "Empty Body"
            )
        }
    }

    fun ignoreUnknownMapper(): ObjectMapper =
        jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
}

data class SignUpRequest(
    val first_name: String,
    val last_name: String,
    val email: String,
    val password: String
)

data class SignUpResponse(
    val id: Int,
    val first_name: String,
    val last_name: String,
    val email: String
)

app build.gradle:

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.example.retrofitbodycrash"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.core:core-ktx:1.1.0-alpha05'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    // Rx
    api "io.reactivex.rxjava2:rxjava:2.2.0"
    implementation "io.reactivex.rxjava2:rxandroid:2.1.0"

    // Jackson
    implementation "com.fasterxml.jackson.core:jackson-core:2.9.8"
    implementation "com.fasterxml.jackson.core:jackson-databind:2.9.8"
    implementation "com.fasterxml.jackson.core:jackson-annotations:2.9.8"
    implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.8"

    // Retrofit
    api "com.squareup.retrofit2:adapter-rxjava2:2.5.0"
    api "com.squareup.retrofit2:converter-jackson:2.5.0"
    api "com.squareup.retrofit2:converter-gson:2.5.0"
    api "com.squareup.retrofit2:retrofit:2.5.0"
    api "com.squareup.okhttp3:logging-interceptor:3.12.0"

    testImplementation 'junit:junit:4.12'
    testImplementation 'com.squareup.okhttp3:mockwebserver:3.12.0'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}

Zipped version of the sample project
RetrofitBodyCrash.zip

@TTransmit TTransmit changed the title Crash with @Body and JacksonConverterFactory: java.lang.NoSuchMethodError: No virtual method getAnnotationsByType Crash with @Body and JacksonConverterFactory: java.lang.NoSuchMethodError: No virtual method getAnnotationsByType Apr 10, 2019
@TTransmit TTransmit changed the title Crash with @Body and JacksonConverterFactory: java.lang.NoSuchMethodError: No virtual method getAnnotationsByType Crash with @Body and JacksonConverterFactory: java.lang.NoSuchMethodError: No virtual method getAnnotationsByType Apr 10, 2019
@JakeWharton
Copy link
Collaborator

Method.getAnnotationsByType was added in Java 8 / Android API 26. The library which is calling the method (Jackson's Kotlin module I guess) likely only supports Java 8 or newer and thus will not work on prior versions of Java or Android prior to API 26.

You'll have to ask them if they're willing to support older versions. This isn't a problem with Retrofit itself.

@TTransmit
Copy link
Author

TTransmit commented Apr 10, 2019

Thanks, I see the crash doesn't happen when rolling back to Jackson version 2.9.6.

There is already an issue raised for this in the jackson-kotlin-module porject: FasterXML/jackson-module-kotlin#176

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants