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
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,
// ...
}
Check If Installed
The launcher checks if the required runtime is already installedsrc-tauri/src/minecraft/java/jre_downloader.rs
let runtime_path = runtimes_folder.join(
format!("{}_{}", jre_distribution.get_name(), jre_version)
);
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?;
// ...
}
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"),
}
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"))
}
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