Cannot inline bytecode JVM 1.8 into 1.6: Under the Hood Analysis

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: When the Compiler Says "No"



Have you ever seen this scary red line in the console? Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. If so — you are not alone. This error has gathered thousands of views on Stack Overflow and has become a real stumbling block for Kotlin developers. But what if I told you that behind this message lies not just a bug, but a whole story of the Java virtual machine's evolution?



In this article, we won't just show you how to fix the error. We will look under the hood of the Kotlin compiler, understand what JVM target is, how inline functions work, and why bytecode version 1.8 doesn't want to be friends with version 1.6. Buckle up — it's going to be interesting. And yes, we will write in Kotlin, because it's not only trendy but also damn convenient.



What is JVM Target and Why Does It Matter?



First, let's understand the basic concepts. JVM (Java Virtual Machine) is the environment where your Kotlin code runs. Each JVM version can understand a specific version of bytecode. For example:

  • JVM target 1.6 — bytecode compatible with Java 6. This is an old but stable version.
  • JVM target 1.8 — bytecode for Java 8. Adds support for lambdas, Stream API, new annotations, and other features.

When you write kotlin { jvmTarget = "1.8" } in build.gradle.kts, you tell the compiler: "Generate bytecode that JVM 1.8 will understand." And if you specify "1.6" — the bytecode will be compatible with older versions.



The problem arises when different parts of your project (or libraries) are compiled with different JVM targets. The Kotlin compiler sees the mismatch and panics. This is especially true for inline functions, which in Kotlin are a powerful optimization tool.



Inline Functions: How Do They Work Under the Hood?



In Kotlin, the inline keyword forces the compiler to embed the function's code directly at the call site. This differs from regular functions, where a call is made via the stack. Inline functions speed up execution (especially with lambdas), but create one problem: the function's bytecode is copied into every call.



Imagine you have a library compiled with JVM target 1.8. Inside it, there is an inline function that uses invokedynamic (an instruction that appeared in Java 7/8 for lambdas). You include this library in a project with JVM target 1.6. The compiler tries to inline bytecode with invokedynamic into a context where such an instruction does not exist. And it gets an error.



// Example of an inline function in a library (compiled with target 1.8)inline fun performAction(action: () -> Unit) {    println("Before")    action()    println("After")}

// Your code (compiled with target 1.6)fun main() { performAction { println("Hello!") } // ERROR: Cannot inline bytecode...}


The compiler screams: "Hey, I can't copy this bytecode, it uses JVM 1.8 features, but my target is 1.6!"



Why Does This Happen in Real Projects?



The most common reason is a version conflict in Gradle dependencies. For example:

  • You use the Kotlin coroutines library (which is compiled with target 1.8)
  • Your project is configured for target 1.6 (possibly due to an outdated template or compatibility requirements)
  • During compilation, Kotlin tries to inline coroutine bytecode and encounters incompatibility


Another situation is direct calls to inline functions from different modules. If module A is compiled with target 1.8, and module B with target 1.6, and you call an inline function from A in B, the error is guaranteed.



What It Looks Like in Practice: Example Analysis



Let's simulate the situation. Let's create a simple Kotlin project with Gradle.



// build.gr                

Blogs

Book Recommendations