What do Kotlin compiled classes look like to the Java runtime? Here is a commented log of compiling and examining Kotlin classes of the Expr example in jshell, Java 9's new command-line environment. My comments start with // . Jshell is what's called a REPL, Read-Eval-Print Loop, for Java. Unlike Python, Ruby, Perl, and other modern languages Java did not come with an official REPL prior to Java 9. Now it's here, so that classes can be loaded, their methods can be called, and expressions can be evaluated without having to compile a new class with a main(..) and only then running it. // Compile Kotlin into Java .class files // It's important to compile Kotlin classes with -include-runtime. // Otherwise you'll errors such as // java.lang.NoClassDefFoundError thrown: kotlin/jvm/internal/Intrinsics // in unexpected places. // Since the example compiled here includes a package specification, // the compiled classes go into a series of nested folders that // correspond to the package name. It's more convenient to // compile this directory tree into a JAR rather than creating // all these folders & class files. $ kotlinc -include-runtime -d expr1.jar expr1.kt // Ignore this warning. It comes from a Java 9 regression. WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by com.intellij.util.text.StringFactory to constructor java.lang.String(char[],boolean) WARNING: Please consider reporting this to the maintainers of com.intellij.util.text.StringFactory WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release // The classes and folders created by this compilation are these: // com/netfluke/calc/Const.class // com/netfluke/calc/Expr$Companion.class // com/netfluke/calc/Expr.class // com/netfluke/calc/NotANumber.class // com/netfluke/calc/Prod.class // com/netfluke/calc/Sum.class // META-INF/main.kotlin_module // META-INF/MANIFEST.MF // which corresponds to the usual Java one file per class convention. // However, the JAR file also contains many more classes that corrspond // to Kotlin's types, needed for these classes to run. Hence the // necessity to compile with "-include-runtime". See all these classes // by running "unzip -l expr1.jar". Since JAR files are basically ZIP // files with the additional manifest (the contents of META-INF/), // "unzip -l" will show the file names and path in this JAR. // Now load the JAR into Jshell: $ jshell --class-path=./expr1.jar | Welcome to JShell -- Version 9 | For an introduction type: /help intro // Although these classes are now in the Java classpath, they // are not automatically imported. We need to import them: jshell> import com.netfluke.calc.Const jshell> import com.netfluke.calc.Expr jshell> import com.netfluke.calc.Sum // Now we can create objects of these classes: jshell> new Sum(new Const(1.0), new Const(1.0)) $4 ==> Sum(e1=Const(number=1.0), e2=Const(number=1.0)) // The eval(..) function is in the companion object of Expr. // This is the "Expr$Companion" class above. In Kotlin, // you can call this function as Expr.eval(..), but // on the Java side the existence of the companion class // is not hidden. Recall that Koltin does not have class // methods, and must simulate them by creating an object // that is unique to the class (called the "companion" object). // But see below for how you can get rid of this extra "Companion" // part. jshell> Expr.Companion.eval(new Const(1.0)) $5 ==> 1.0 jshell> Expr.Companion.eval(new Sum(new Const(1.0), new Const(1.0))) $6 ==> 2.0 jshell> Const a = new Const(2.0) a ==> Const(number=2.0) // Let's use Java reflection API to see what methods these Kotlin-compiled // data classes have. They sure have a toString() that is used // to pretty-print them: jshell> a a ==> Const(number=2.0) // We can't get at a's "number" member directly, because it is // automatically made private: jshell> a.number | Error: | number has private access in com.netfluke.calc.Const | a.number | ^------^ // So for reading a Const's "number", we get the getter accessor getNumber(): jshell> a.getNumber() $9 ==> 2.0 // ...but we don't get a setter, because "number" is declared as a val, // not a var (i.e., immutable, not be be set); jshell> a.setNumber(3.0) | Error: | cannot find symbol | symbol: method setNumber(double) | a.setNumber(3.0) | ^---------^ // So let's get a full list of methods that "a" supports. // First, let's get the class object for a: jshell> a.class | Error: | cannot find symbol | symbol: class a | a.class | ^ // Oops, that doesn't work. We need a getClass(), the Java-style accessor. jshell> a.getClass() $10 ==> class com.netfluke.calc.Const // OK, that worked. Now let's get the methods: jshell> a.getClass().getMethods() $11 ==> Method[13] { public boolean com.netfluke.calc.Const.equals(java.lang.Object), public java.lang.String com.netfluke.calc.Const.toString(), public int com.netfluke.calc.Const.hashCode(), public final com.netfluke.calc.Const com.netfluke.calc.Const.copy(double), public final double com.netfluke.calc.Const.component1(), public static com.netfluke.calc.Const com.netfluke.calc.Const.copy$default(com.netfluke.calc.Const,double,int,java.lang.Object), public final double com.netfluke.calc.Const.getNumber(), public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final void java.lang.Object.wait() throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll() } // So we have it, but it's not very readable. Let's see if we can // traverse this array more conveniently. For this, we need to // convert it to a List first; then we can filter that List. // If you know Java 8's streams and filtering API, stop here. // For examples of these API, see // https://www.mkyong.com/java8/java-8-streams-filter-examples/ // Note that what we got back was not a List, but an array, Method[]: | incompatible types: java.lang.reflect.Method[] cannot be converted to java.util.List | a.getClass().getMethods() instanceof List | ^-----------------------^ // So let's get a List: jshell> Arrays.asList(a.getClass().getMethods()) $15 ==> [public boolean com.netfluke.calc.Const.equals(java.lang.Object), public java.lang.String com.netfluke.calc.Const.toString(), public int com.netfluke.calc.Const.hashCode(), public final com.netfluke.calc.Const com.netfluke.calc.Const.copy(double), public final double com.netfluke.calc.Const.component1(), public static com.netfluke.calc.Const com.netfluke.calc.Const.copy$default(com.netfluke.calc.Const,double,int,java.lang.Object), public final double com.netfluke.calc.Const.getNumber(), public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final void java.lang.Object.wait() throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()] // ..and then make it a "stream" that we can filter with Java's new // filter(..) function: jshell> Arrays.asList(a.getClass().getMethods()).stream() $16 ==> java.util.stream.ReferencePipeline$Head@370736d9 // ...say, to find all methods that start with "e": jshell> Arrays.asList(a.getClass().getMethods()).stream().filter( (Method m) -> m.getName().startsWith("e") ) | Error: | cannot find symbol | symbol: class Method | Arrays.asList(a.getClass().getMethods()).stream().filter( (Method m) -> m.getName().startsWith("e") ) | ^----^ // Ouch, I forgot to import "Method", so Jshell balks at taking // it as an argument type declaration (even though a previous output // was recognized as Method[]). Correct this: jshell> import java.lang.reflect.Method jshell> Arrays.asList(a.getClass().getMethods()).stream().filter( (Method m) -> m.getName().startsWith("e") ) $18 ==> java.util.stream.ReferencePipeline$2@6c3708b3 // Well, we aren't done yet. Our stream has been filtered but the // result is not a List; we must use .collect(..) for that: jshell> Arrays.asList(a.getClass().getMethods()).stream().filter( (Method m) -> m.getName().startsWith("e") ).collect(Collectors.toList()) $19 ==> [public boolean com.netfluke.calc.Const.equals(java.lang.Object)] // Yes, this API is twice as complex as it needs to be :( // ..and just in case you want to find just one element that matches, // Java 9 also has .findAny().orElse(null) API, which is also // quite a mouthful: jshell> Arrays.asList(a.getClass().getMethods()).stream().filter( (Method m) -> m.getName().startsWith("e") ).findAny() $20 ==> Optional[public boolean com.netfluke.calc.Const.equals(java.lang.Object)] jshell> Arrays.asList(a.getClass().getMethods()).stream().filter( (Method m) -> m.getName().startsWith("e") ).findAny().orElse(null) $21 ==> public boolean com.netfluke.calc.Const.equals(java.lang.Object) // ..another example of filtering: jshell> Arrays.asList(a.getClass().getMethods()).stream().filter( (Method m) -> m.getName().startsWith("to") ).collect(Collectors.toList()) $22 ==> [public java.lang.String com.netfluke.calc.Const.toString()] // So let's get all the getters: jshell> Arrays.asList(a.getClass().getMethods()).stream().filter( (Method m) -> m.getName().startsWith("get") ).collect(Collectors.toList()) $23 ==> [public final double com.netfluke.calc.Const.getNumber(), public final native java.lang.Class java.lang.Object.getClass()] // ..and all the setters: jshell> Arrays.asList(a.getClass().getMethods()).stream().filter( (Method m) -> m.getName().startsWith("set") ).collect(Collectors.toList()) $24 ==> [] jshell> In-depth links on this topic: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html Kotlin gives you a lot of control of how its classes look to the Java side. For example, you can get rid of the unsightly Companion reference Expr.Companion.eval(..) in the expr1.kt example with the @JvmStatic annotation, and just have Expr.eval(..). Tutorials on Jshell: http://jakubdziworski.github.io/java/2016/07/31/jshell-getting-started-examples.html -- short and sweet, but with some fun nuggets http://cr.openjdk.java.net/~rfield/tutorial/JShellTutorial.html -- in-depth