1. Introduction

In this tutorial, we’ll explore how to access Kotlin companion objects using Java Reflection API. We’ll first look at what the companion object actually compiles into, and then we’ll try to access it via reflection.

2. Kotlin Code Sample

So, let’s write a code sample in Kotlin for our demonstration:

class XmlParsingService private constructor() {

    companion object {
        private var factory: DocumentBuilderFactory = DocumentBuilderFactory.newDefaultInstance()

        fun extractIdFromXmlEntity(xml: String): UUID {
            val document: Document = factory.newDocumentBuilder().parse(ByteArrayInputStream(xml.toByteArray()))
            val node: Node = document.getElementsByTagName("entityId").item(0)
            return UUID.fromString(node.textContent)
        }
    }
}

As we can see, there is an XmlParsingService utility class that is capable of extracting the entityId from the XML. The point here is that the method extractIdFromXmlEntity() belongs to the companion object. So, our goal would be to get the signature of this method in Java.

3. Companion Object Bytecode

Before we dive into the bytecode, let’s initially discuss the compilation of companion objects from Kotlin to Java bytecode overall. As of major version 1, there is no static keyword in Kotlin. There is a lot of discussion about the possible companion object deprecation in favor of static in Kotlin. But still, we have it now, and we have to work with it.

The companion object is implemented into a nested static class within our parent class. Its name is computed via enclosing class simple name, dollar sign, and the word Companion at the end. All the methods are enclosed in this companion object nested static class. In our case, it is just one method extractIdFromXmlEntity(). The fields themselves, however, are compiled into static fields of an original class and, therefore, can easily be accessed by companion objects.

4. Accessing Companion Object via Reflection

Now, let’s get our hands dirty. We’ll compile our code using kotlinc and then look at the bytecode. For this purpose, we’ll use standard javap utility. Let’s look at the class definition first:

public final class io.courses.baeldung.XmlParsingService
  minor version: 0
  major version: 55
  flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER
  this_class: #2                          // io/courses/baeldung/XmlParsingService
  super_class: #4                         // java/lang/Object
  interfaces: 0, fields: 2, methods: 2, attributes: 3

As we can notice, there are 2 fields declared in the parent class. The first is the DocumentBuilderFactory static field, according to what we discussed before. Another one, however, is also a static field – it is an instance of the companion object itself. So technically, we can access these 2 fields via Java Reflection API fairly easily:

Class<XmlParsingService> xmlParsingServiceClass = XmlParsingService.class;
Field factory = xmlParsingServiceClass.getDeclaredField("factory");
Assertions.assertThat(factory.getType()).isSameAs(DocumentBuilderFactory.class);

So now, let’s take a look at the bytecode of the nested static class itself:

Classfile /home/user/baeldung/kotlin/target/classes/io/courses/baeldung/XmlParsingService$Companion.class
  Last modified Sep 3, 2023; size 2710 bytes
  SHA-256 checksum 2695ee7d098d834f647bbac2f7e069cd4303367ae9138e09733f52b43403f48e
  Compiled from "XmlParsingService.kt"
public final class io.courses.baeldung.XmlParsingService$Companion
  minor version: 0
  major version: 55
  flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER
  this_class: #2                          // io/courses/baeldung/XmlParsingService$Companion
  super_class: #4                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 5, attributes: 3

As we can notice, the name of the class follows the pattern we discussed beforehand. Having that, we can try to load this class using the Class.getClasses() method:

Class<XmlParsingService> xmlParsingServiceClass = XmlParsingService.class;
Class<?>[] classes = xmlParsingServiceClass.getClasses();
Assertions.assertThat(Arrays.asList(classes))
  .hasSize(1)
  .asList()
  .first()
  .isEqualTo(Class.forName("io.courses.baeldung.XmlParsingService$Companion"));

Finally, let’s invoke the method extractIdFromXmlEntity() of the companion object. To do so, we need first to get the instance of the companion object and then execute the method on it:

Class<XmlParsingService> xmlParsingServiceClass = XmlParsingService.class;
Field companion = xmlParsingServiceClass.getDeclaredField("Companion");
companion.setAccessible(true);
Object companionInstance = companion.get(null);
Class<?> companionClass = Class.forName("io.courses.baeldung.XmlParsingService$Companion");
Method extractIdFromXmlEntity = companionClass.getDeclaredMethod("extractIdFromXmlEntity", String.class);
extractIdFromXmlEntity.setAccessible(true);
Object result = extractIdFromXmlEntity.invoke(
  companionInstance, 
  "<entities><entityId>8d15c2f7-635f-4730-ad60-92e2b117c4bc</entityId></entities>"
);
Assertions.assertThat(result).isEqualTo(UUID.fromString("8d15c2f7-635f-4730-ad60-92e2b117c4bc"));

This is how we can execute the method declared in the companion object via Java Reflection API.

5. Conclusion

In this tutorial, we’ve explored how to access Kotlin companion objects via Java Reflection API. The companion object compiles into a nested static class, an instance of which is present in the enclosing class. This static nested class contains all methods from the original companion object.

As always, the source code for this article can be found over on GitHub.

Comments are closed on this article!