Kotlin override ambiguity

Viewed 138

I have to create a custom logging provider for my Quarkus application to convert the log entries to the Google Cloud JSON format.
For this, the provider class (which is written in Kotlin) has to extend an abstract class called ExtFormatter (from a 3rd-party lib, written in Java), which has these two methods:

package org.jboss.logmanager;
[...]

public abstract class ExtFormatter extends Formatter {
   public final String format(final LogRecord record) {
      return format(ExtLogRecord.wrap(record));
   }

   [...]
}

public abstract String format(ExtLogRecord record);

ExtLogRecord is a subclass of LogRecord, so the method signatures are quite similar.
My logging provider implements the abstract method like this:

import io.quarkiverse.loggingjson.JsonProvider
import org.jboss.logmanager.ExtFormatter
import org.jboss.logmanager.ExtLogRecord
[...]

class JsonLoggingProvider : JsonProvider, ExtFormatter() {
   override fun format(extLogRecord: ExtLogRecord?) = null
}

When I run the application (not at compile time), I get this error:

java.lang.VerifyError: class com.mycompany.logging.JsonLoggingProvider_Subclass overrides final method org.jboss.logmanager.ExtFormatter.format(Ljava/util/logging/LogRecord;)Ljava/lang/String;
        at java.base/java.lang.ClassLoader.defineClass1(Native Method)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:437)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:397)
        at com.mycompany.logging.JsonLoggingProvider_Bean.create(JsonLoggingProvider_Bean.zig:368)
        at com.mycompany.logging.JsonLoggingProvider_Bean.create(JsonLoggingProvider_Bean.zig:394)
        at io.quarkus.arc.impl.AbstractSharedContext.createInstanceHandle(AbstractSharedContext.java:96)
        at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:29)
        at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:26)
        at io.quarkus.arc.impl.LazyValue.get(LazyValue.java:26)
        at io.quarkus.arc.impl.ComputingCache.computeIfAbsent(ComputingCache.java:69)
        at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:26)
        at com.mycompany.logging.JsonLoggingProvider_Bean.get(JsonLoggingProvider_Bean.zig:426)
        at com.mycompany.logging.JsonLoggingProvider_Bean.get(JsonLoggingProvider_Bean.zig:442)
        at io.quarkus.arc.impl.InstanceImpl.getBeanInstance(InstanceImpl.java:210)
        at io.quarkus.arc.impl.InstanceImpl$InstanceIterator.next(InstanceImpl.java:246)
        at java.base/java.lang.Iterable.forEach(Iterable.java:74)
        at io.quarkiverse.loggingjson.LoggingJsonRecorder.initializeJsonLogging(LoggingJsonRecorder.java:62)
        at io.quarkus.deployment.steps.LoggingJsonProcessor$setUpFormatter-614569086.deploy_0(LoggingJsonProcessor$setUpFormatter-614569086.zig:88)
        at io.quarkus.deployment.steps.LoggingJsonProcessor$setUpFormatter-614569086.deploy(LoggingJsonProcessor$setUpFormatter-614569086.zig:40)
        at io.quarkus.runner.ApplicationImpl.doStart(ApplicationImpl.zig:666)
        at io.quarkus.runtime.Application.start(Application.java:101)
        at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:101)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:66)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:42)
        at io.quarkus.runtime.Quarkus.run(Quarkus.java:119)
        at io.quarkus.runner.GeneratedMain.main(GeneratedMain.zig:29)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:98)
        at java.base/java.lang.Thread.run(Thread.java:829)

For some reason the wrong method (or both methods?) seem to be overridden.
I suspect that it happens because the methods have more or less the same signature. Is there a way to make the Kotlin override more precise to avoid this issue?

1 Answers

In contrast to my initial assumption, the problem is apparently not caused by Kotlin, but by Quarkus. Dependency injection seems to be confused by the very similar methods. Since I cannot change any method names or signatures because they're from the 3rd party Quarkiverse library, I worked around the issue by no longer subclassing ExtFormatter, but instead instantiating an already existing subclass of it called JsonFormatter:

import io.quarkiverse.loggingjson.JsonGenerator
import io.quarkiverse.loggingjson.JsonProvider
import org.jboss.logmanager.ExtLogRecord
import org.jboss.logmanager.formatters.JsonFormatter
[...]

class JsonLoggingProvider : JsonProvider {
  private val jsonFormatter = JsonFormatter()
  
  private fun writeMessage(generator: JsonGenerator, event: ExtLogRecord) {
      val message = jsonFormatter.formatMessage(event)
      [...]
  }
}

Thanks again broot for helping me out!

Related