Skip to main content
LiquidLauncher automatically downloads and manages Java runtimes, ensuring the correct version is always available.

Supported Distributions

The launcher supports three major Java distributions:

Eclipse Temurin

Default distribution
  • Supports all LTS versions (8, 11, 17, 21)
  • AdoptOpenJDK successor
  • Excellent compatibility

GraalVM

Performance focused
  • Java 17+ only
  • Advanced optimizations
  • Native image support

Azul Zulu

Enterprise ready
  • All LTS versions
  • Commercial support available
  • Wide platform support

Distribution Selection

src-tauri/src/minecraft/java/distribution.rs
#[derive(Deserialize, Serialize, Clone)]
pub enum JavaDistribution {
    #[serde(rename = "temurin")]
    Temurin,
    #[serde(rename = "graalvm")]
    GraalVM,
    #[serde(rename = "zulu")]
    Zulu,
}

impl Default for JavaDistribution {
    fn default() -> Self {
        // Temurin supports any version of java
        JavaDistribution::Temurin
    }
}
The default distribution (Temurin) is automatically selected and supports all Java versions.

Automatic Download

Java runtimes are downloaded automatically when launching a build that requires them.

Download Process

1

Determine Required Version

Each build specifies its required Java version:
src-tauri/src/app/client_api.rs
pub struct Build {
    pub jre_distribution: JavaDistribution,
    pub jre_version: u32,
    // ...
}
2

Check If Installed

The launcher checks if the required runtime is already installed
src-tauri/src/minecraft/java/jre_downloader.rs
let runtime_path = runtimes_folder.join(
    format!("{}_{}", jre_distribution.get_name(), jre_version)
);
3

Download Runtime

If not present, downloads from the distribution’s CDN:
src-tauri/src/minecraft/java/jre_downloader.rs
pub async fn jre_download<F>(
    runtimes_folder: &Path,
    jre_distribution: &JavaDistribution,
    jre_version: &u32,
    on_progress: F,
) -> Result<PathBuf>
where
    F: Fn(u64, u64),
{
    let url = jre_distribution.get_url(jre_version).await?;
    let retrieved_bytes = download_file(&url, on_progress).await?;
    // ...
}
4

Extract Archive

Extracts the downloaded archive (ZIP on Windows, TAR.GZ on Linux/macOS):
src-tauri/src/minecraft/java/jre_downloader.rs
match OS {
    OperatingSystem::WINDOWS => zip_extract(cursor, runtime_path.as_path()).await?,
    OperatingSystem::LINUX | OperatingSystem::OSX => {
        tar_gz_extract(cursor, runtime_path.as_path()).await?
    }
    _ => bail!("Unsupported OS"),
}
5

Locate Binary

Finds and verifies the Java executable

Download URLs

Each distribution has its own download URL format:
JavaDistribution::Temurin => {
    let os_name = OS.get_adoptium_name()?;
    format!(
        "https://api.adoptium.net/v3/binary/latest/{}/ga/{}/{}/jre/hotspot/normal/eclipse?project=jdk",
        jre_version, os_name, os_arch
    )
}

Java Binary Location

The launcher automatically locates the Java binary based on the operating system:
src-tauri/src/minecraft/java/jre_downloader.rs
pub async fn find_java_binary(
    runtimes_folder: &Path,
    jre_distribution: &JavaDistribution,
    jre_version: &u32,
) -> Result<PathBuf> {
    let runtime_path = runtimes_folder.join(
        format!("{}_{}", jre_distribution.get_name(), jre_version)
    );

    let mut files = fs::read_dir(&runtime_path).await?;

    if let Some(jre_folder) = files.next_entry().await? {
        let folder_path = jre_folder.path();

        let java_binary = match OS {
            OperatingSystem::WINDOWS => folder_path.join("bin").join("javaw.exe"),
            OperatingSystem::OSX => folder_path
                .join("Contents")
                .join("Home")
                .join("bin")
                .join("java"),
            _ => folder_path.join("bin").join("java"),
        };

        if java_binary.exists() {
            return Ok(java_binary.absolutize()?.to_path_buf());
        }
    }

    Err(anyhow::anyhow!("Failed to find JRE"))
}
  • Windows: bin/javaw.exe (windowed executable, no console)
  • macOS: Contents/Home/bin/java (app bundle structure)
  • Linux: bin/java (standard Unix layout)

Permissions Management

On Unix systems, the launcher ensures the Java binary has execution permissions:
src-tauri/src/minecraft/java/jre_downloader.rs
#[cfg(unix)]
{
    use std::os::unix::fs::PermissionsExt;

    let metadata = fs::metadata(&java_binary).await?;

    if !metadata.permissions().mode() & 0o111 != 0 {
        // try to change permissions
        let mut permissions = metadata.permissions();
        permissions.set_mode(0o111);
        fs::set_permissions(&java_binary, permissions).await?;
    }
}
This ensures downloaded Java runtimes are immediately executable without manual permission changes.

Runtime Execution

The JavaRuntime wrapper handles process execution:
src-tauri/src/minecraft/java/runtime.rs
pub struct JavaRuntime(PathBuf);

impl JavaRuntime {
    pub async fn execute(&self, arguments: Vec<String>, game_dir: &Path) -> Result<Child> {
        if !self.0.exists() {
            bail!("Java runtime not found at: {}", self.0.display());
        }

        debug!("Executing Java runtime: {}", self.0.display());

        let mut command = Command::new(&self.0);
        command.current_dir(game_dir);
        command.args(arguments);

        command.stderr(Stdio::piped()).stdout(Stdio::piped());

        let child = command.spawn()?;
        Ok(child)
    }
}

IO Handling

The launcher captures and processes stdout/stderr:
src-tauri/src/minecraft/java/runtime.rs
pub async fn handle_io<D: Send + Sync>(
    &self,
    running_task: &mut Child,
    on_stdout: fn(&D, &[u8]) -> Result<()>,
    on_stderr: fn(&D, &[u8]) -> Result<()>,
    terminator: Receiver<()>,
    data: &D,
) -> Result<()> {
    let mut stdout = running_task.stdout.take().unwrap();
    let mut stderr = running_task.stderr.take().unwrap();

    loop {
        tokio::select! {
            read_len = stdout.read(&mut stdout_buf) => {
                let _ = on_stdout(&data, &stdout_buf[..read_len?]);
            },
            read_len = stderr.read(&mut stderr_buf) => {
                let _ = on_stderr(&data, &stderr_buf[..read_len?]);
            },
            _ = &mut terminator => {
                running_task.kill().await?;
                break;
            },
            exit_status = running_task.wait() => {
                let code = exit_status?.code().unwrap_or(7900);
                if code != 0 && code != -1073740791 {
                    bail!("Process exited with non-zero exit code: {}.", code);
                }
                break;
            },
        }
    }
    Ok(())
}

Version Support

Each distribution has different version support:
src-tauri/src/minecraft/java/distribution.rs
pub fn supports_version(&self, version: u32) -> bool {
    match self {
        JavaDistribution::Temurin => true, // Supports 8, 11, 17, 21
        JavaDistribution::GraalVM => version >= 17, // Only supports 17+
        JavaDistribution::Zulu => true, // Community builds for all LTS versions
    }
}
If a build requires Java 8, GraalVM will automatically fall back to Temurin.

Custom Java Path

Advanced users can specify a custom Java installation:
src-tauri/src/app/options.rs
pub struct StartOptions {
    pub java_distribution: DistributionSelection,
    // ...
}

#[derive(Deserialize, Serialize, Clone)]
#[serde(tag = "type", content = "value")]
pub enum DistributionSelection {
    #[serde(rename = "automatic")]
    Automatic(String),
    #[serde(rename = "custom")]
    Custom(String),
    #[serde(rename = "manual")]
    Manual(JavaDistribution),
}
  • Automatic: Launcher automatically selects the best distribution
  • Manual: User chooses a specific distribution (Temurin, GraalVM, Zulu)
  • Custom: User provides path to existing Java installation

Storage

Java runtimes are stored in the launcher’s data directory:
~/.liquidlauncher/runtimes/
├── temurin_8/
├── temurin_17/
├── temurin_21/
├── graalvm_17/
└── zulu_21/
Each runtime is isolated by distribution and version to prevent conflicts.

Version Selection

Each build specifies its required Java version

Auto-Updates

Java versions are updated with build updates

Build docs developers (and LLMs) love