Skip to main content

Overview

Compose Hot Reload requires JetBrains Runtime (JBR) to function. This page explains why JBR is necessary, how to obtain it, and how to configure it for your project.

Why JetBrains Runtime?

JetBrains Runtime is a fork of OpenJDK that includes enhanced class redefinition capabilities necessary for hot reload to work.

Standard JDK Limitations

The standard JDK’s HotSwap feature has severe restrictions:
  • ❌ Can only redefine method bodies
  • Cannot add or remove methods
  • Cannot add or remove fields
  • Cannot change method signatures
  • Cannot modify class hierarchy (superclass, interfaces)
  • Cannot change class modifiers (e.g., public to private)
These limitations make standard HotSwap nearly useless for real-world development:
// Standard JDK HotSwap can handle this:
fun greet() {
    println("Hello")  // ✓ Can change to "Hi"
}

// But CANNOT handle any of these:
fun greet(name: String) {  // ✗ Changed signature
    println("Hello $name")
}

val greeting = "Hello"  // ✗ Added field

fun newFunction() {}  // ✗ Added method

class Greeter : NewBase()  // ✗ Changed superclass

JetBrains Runtime Capabilities (DCEVM)

JetBrains Runtime includes Dynamic Code Evolution VM (DCEVM), which enables:
  • Add/remove methods - Define new functions or delete existing ones
  • Add/remove fields - Add new properties or remove old ones
  • Change method signatures - Modify parameters, return types
  • Modify class hierarchy - Change superclass and interfaces (with some limitations)
  • Change class modifiers - Update visibility and other modifiers
  • Redefine enum values - Add or remove enum constants (with limitations)
This makes true hot reload possible:
@Composable
fun MyScreen() {
    // All of these changes can be hot reloaded:
    var count by remember { mutableStateOf(0) }  // ✓ Added field
    
    Button(                                      // ✓ Changed UI
        onClick = { count++ },                   // ✓ Changed logic
        colors = ButtonDefaults.buttonColors()   // ✓ Added parameter
    ) {
        Text("Clicked $count times")            // ✓ Changed text
    }
}

Enhanced Class Redefinition

JBR’s enhanced redefinition is enabled via:
-XX:+AllowEnhancedClassRedefinition
This JVM flag is:
  • Only available in JetBrains Runtime
  • Automatically added by the Compose Hot Reload Gradle plugin
  • Required for hot reload to work
Attempting to run hot reload with a standard JDK will fail with an error about the unrecognized VM option.

Obtaining JetBrains Runtime

There are multiple ways to obtain JBR, with automatic fallback if one method fails.

Method 1: Automatic Provisioning (Experimental)

The Gradle plugin can automatically download and provision JBR. Enable in gradle.properties:
compose.reload.jbr.autoProvisioningEnabled=true
How it works:
  1. Plugin detects your project’s Java target version
  2. Downloads matching JBR from JetBrains servers
  3. Extracts to Gradle user home: ~/.gradle/chr/jbr/
  4. Caches for reuse across projects
  5. Uses file locking to prevent concurrent downloads
Supported versions:
  • JBR 11 (Java 11)
  • JBR 17 (Java 17)
  • JBR 21 (Java 21) - Default
  • JBR 25 (Java 25)
The provisioner selects the JBR version that matches your project’s Java toolchain:
kotlin {
    jvmToolchain(21)  // Will use JBR 21
}
Automatic provisioning is experimental. If you encounter issues, please report them on GitHub.
Advantages:
  • Zero manual setup
  • Automatic version matching
  • Shared across projects
Disadvantages:
  • Requires internet connection (first time only)
  • Experimental feature
  • Limited control over exact build

Method 2: Gradle Toolchain with Foojay

Use Gradle’s toolchain support with the Foojay resolver to automatically download JBR. 1. Add foojay plugin to settings.gradle.kts:
plugins {
    id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
2. Enable JBR provisioning in gradle.properties:
compose.reload.gradle.jbr.provisioningEnabled=true
3. Optionally specify version:
compose.reload.jbr.version=21
How it works:
  1. Gradle’s toolchain service requests a JetBrains JVM
  2. Foojay resolver searches for matching JBR distributions
  3. Downloads from foojay.io (a JVM discovery service)
  4. Installs to Gradle’s toolchain directory
  5. Reused automatically for subsequent runs
Advantages:
  • Uses standard Gradle mechanism
  • Well-tested and stable
  • Works for all Gradle tasks, not just hot reload
Disadvantages:
  • Requires foojay plugin
  • Less control over specific JBR builds

Method 3: IntelliJ IDEA Integration

When running hot reload from IntelliJ IDEA or Android Studio with the Kotlin Multiplatform plugin: The IDE automatically provides JBR:
  1. IntelliJ bundles JetBrains Runtime
  2. Plugin detects the bundled JBR
  3. Passes JBR path to Gradle via system property
  4. Hot reload tasks use IDE’s JBR automatically
This works out-of-the-box with:
  • IntelliJ IDEA 2025.2.2+ (any edition)
  • Android Studio Otter 2025.2.1+
No configuration needed when running from the IDE!
This is the recommended approach for most developers. Install a recent IDE and run hot reload from the gutter icon.

Method 4: Manual Configuration

You can manually download and configure JBR. 1. Download JBR: Visit the JetBrains Runtime GitHub releases and download a build for your platform:
  • Linux: jbr-<version>-linux-x64.tar.gz
  • macOS: jbr-<version>-osx-x64.tar.gz (Intel) or jbr-<version>-osx-aarch64.tar.gz (Apple Silicon)
  • Windows: jbr-<version>-windows-x64.tar.gz
2. Extract the archive:
tar -xzf jbr-21.0.10-linux-x64.tar.gz -C ~/tools/
3. Configure Gradle to use it: Option A: gradle.properties
compose.reload.jbr.binary=/home/user/tools/jbr-21.0.10/bin/java
Option B: System property
./gradlew hotRunJvm -Dcompose.reload.jbr.binary=/home/user/tools/jbr-21.0.10/bin/java
Option C: Environment variable
export COMPOSE_RELOAD_JBR_BINARY=/home/user/tools/jbr-21.0.10/bin/java
./gradlew hotRunJvm
Option D: build.gradle.kts
tasks.withType<ComposeHotRun>().configureEach {
    javaLauncher.set(
        javaToolchains.launcherFor {
            languageVersion.set(JavaLanguageVersion.of(21))
            vendor.set(JvmVendorSpec.JETBRAINS)
        }
    )
}
Advantages:
  • Full control over JBR version and build
  • Can use custom or patched JBR builds
  • Works offline
Disadvantages:
  • Manual download and updates
  • Must manage multiple versions for different projects
  • Platform-specific setup

Java Version Compatibility

Your project must target Java 21 or earlier to be compatible with JetBrains Runtime.

Checking Your Java Target

In build.gradle.kts:
kotlin {
    jvmToolchain(21)  // ✓ Compatible
}
Or:
java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(21))
    }
}

Supported Java Versions

Java VersionJBR SupportStatus
Java 8⚠️ LimitedNot recommended
Java 11✅ JBR 11Supported
Java 17✅ JBR 17Supported
Java 21✅ JBR 21Recommended
Java 25✅ JBR 25Supported
Java 21 with JBR 21 is the recommended configuration for the best compatibility and features.

Upgrading from Java 8/11

If your project uses Java 8 or 11: Update to Java 17 or 21:
kotlin {
    jvmToolchain(21)  // Changed from 11
}
This ensures compatibility with modern JBR versions and Compose Multiplatform.

Resolution Order

The Gradle plugin searches for JBR in this order:
  1. User-specified: compose.reload.jbr.binary property
  2. Gradle toolchain: Foojay resolver with JetBrains vendor
  3. Auto-provisioned: Automatic download if enabled
  4. IntelliJ JBR: IDE-provided when running from IDEA
  5. Error: No suitable JBR found
The first successful method is used.

Configuration Properties Reference

Gradle Properties

Set in gradle.properties:
# Enable automatic JBR provisioning (experimental)
compose.reload.jbr.autoProvisioningEnabled=true

# Enable Gradle toolchain JBR provisioning
compose.reload.gradle.jbr.provisioningEnabled=true

# Specify JBR version (default: 21)
compose.reload.jbr.version=21

# Provide path to JBR executable
compose.reload.jbr.binary=/path/to/jbr/bin/java

System Properties

Pass at runtime:
# Specify JBR binary
./gradlew hotRunJvm -Dcompose.reload.jbr.binary=/path/to/jbr/bin/java

# Use IntelliJ's JBR (set automatically by IDE plugin)
./gradlew hotRunJvm -Dcompose.reload.idea.jbr.binary=/path/to/idea/jbr/bin/java

# Specify JBR version for auto-provisioning
./gradlew hotRunJvm -Dcompose.reload.jbr.version=21

Environment Variables

export COMPOSE_RELOAD_JBR_BINARY=/path/to/jbr/bin/java
./gradlew hotRunJvm

Verifying JBR Installation

To confirm you’re using JetBrains Runtime: Check the console output when launching:
./gradlew :app:hotRunJvm
Look for:
✓ JetBrains Runtime 21.0.10+1163.105
Or if using a standard JDK:
⚠️ VM: Oracle Corporation (JetBrains Runtime required)
Verify Java version:
/path/to/jbr/bin/java -version
Should output something like:
openjdk version "21.0.10" 2025-01-21
OpenJDK Runtime Environment JBR-21.0.10+1163.105 (build 21.0.10+1163.105)
OpenJDK 64-Bit Server VM JBR-21.0.10+1163.105 (build 21.0.10+1163.105, mixed mode)
Key indicator: JBR in the version string. Test enhanced redefinition:
/path/to/jbr/bin/java -XX:+AllowEnhancedClassRedefinition -version
Should succeed without errors. Standard JDK will fail:
Unrecognized VM option 'AllowEnhancedClassRedefinition'

Platform-Specific Notes

macOS

Unsigned binary warning: First time running manually installed JBR:
xattr -d com.apple.quarantine /path/to/jbr
Apple Silicon: Use the aarch64 (ARM64) build for better performance:
jbr-21.0.10-osx-aarch64.tar.gz
Intel Macs: Use the x64 build:
jbr-21.0.10-osx-x64.tar.gz

Linux

Set executable permissions:
chmod +x /path/to/jbr/bin/java
System packages: JBR requires these libraries (usually pre-installed):
# Debian/Ubuntu
sudo apt-get install libfreetype6 libfontconfig1

# Fedora/RHEL
sudo dnf install freetype fontconfig

Windows

Extract location: Extract to a path without spaces:
C:\tools\jbr-21.0.10  ✓ Good
C:\Program Files\jbr  ✗ Spaces cause issues
Path separators: Use forward slashes or escaped backslashes:
# Good
compose.reload.jbr.binary=C:/tools/jbr-21.0.10/bin/java.exe

# Also good
compose.reload.jbr.binary=C:\\tools\\jbr-21.0.10\\bin\\java.exe

Troubleshooting

”Failed to find suitable JetBrains Runtime”

Problem: No JBR found using any method. Solutions:
  1. Enable automatic provisioning:
    compose.reload.jbr.autoProvisioningEnabled=true
    
  2. Install foojay resolver:
    // settings.gradle.kts
    plugins {
        id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
    }
    
  3. Run from IntelliJ IDEA with Kotlin Multiplatform plugin
  4. Manually download and configure JBR (see Method 4)

“Unrecognized VM option ‘AllowEnhancedClassRedefinition’”

Problem: You’re using a standard JDK instead of JBR. Solution: Install JetBrains Runtime using one of the methods above.

”JBR version mismatch”

Problem: JBR version doesn’t match your Java target. Solution: Update JBR version to match:
compose.reload.jbr.version=21
Or update your Java toolchain:
kotlin {
    jvmToolchain(21)  // Match your JBR version
}

“Cannot download JBR” (Offline mode)

Problem: Automatic provisioning fails in offline mode. Solutions:
  1. Download JBR manually before going offline
  2. Use IntelliJ’s bundled JBR (works offline)
  3. Disable automatic provisioning:
    compose.reload.jbr.autoProvisioningEnabled=false
    

Best Practices

  1. Use IntelliJ IDEA for development - JBR is provided automatically
  2. Enable auto-provisioning for CI/CD environments:
    compose.reload.jbr.autoProvisioningEnabled=true
    
  3. Match Java versions - Use JBR version that matches your target:
    jvmToolchain(21)  // Use JBR 21
    
  4. Don’t commit JBR to version control - It’s large (~300MB)
  5. Document for team - Add setup instructions to your README

Summary

  • JetBrains Runtime is required for Compose Hot Reload
  • Standard JDK cannot perform enhanced class redefinition
  • Multiple provisioning methods available (automatic, Gradle, manual)
  • IntelliJ integration provides JBR automatically
  • Java 21 with JBR 21 is the recommended configuration
  • Target Java 21 or earlier for compatibility
For most developers, using IntelliJ IDEA with the Kotlin Multiplatform plugin provides the best experience with zero JBR configuration needed.

Build docs developers (and LLMs) love