Java has no built-in audio transcoding capability. Extracting MP3 audio from an MP4 video requires FFmpeg — the industry standard for media conversion. JAVE2 packages FFmpeg as a native binary per OS (Linux, macOS, Windows) and provides a Java API on top of it. Alternatively, you can call FFmpeg via ProcessBuilder directly if it's already installed. For cloud deployments without native binaries, the ChangeThisFile API accepts MP4 uploads and returns MP3 from a simple HttpClient POST.
Method 1: JAVE2 (Java FFmpeg wrapper, bundled natives)
JAVE2 bundles FFmpeg as a native binary in the JAR. No system FFmpeg installation required.
<!-- Core + your OS-specific native -->
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-core</artifactId>
<version>3.5.0</version>
</dependency>
<!-- Linux 64-bit -->
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-nativebin-linux64</artifactId>
<version>3.5.0</version>
</dependency>
<!-- Add jave-nativebin-osx64 or jave-nativebin-win64 for other platforms -->
import ws.schild.jave.Encoder;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.encode.AudioAttributes;
import ws.schild.jave.encode.EncodingAttributes;
import java.io.File;
import java.nio.file.Path;
public class Mp4ToMp3 {
public static void convert(Path mp4Path, Path mp3Path, int bitrate) throws Exception {
AudioAttributes audio = new AudioAttributes();
audio.setCodec("libmp3lame"); // MP3 encoder
audio.setBitRate(bitrate); // e.g. 192000 for 192 kbps
audio.setChannels(2); // Stereo
audio.setSamplingRate(44100); // Standard 44.1 kHz
EncodingAttributes attrs = new EncodingAttributes();
attrs.setInputFormat("mp4");
attrs.setOutputFormat("mp3");
attrs.setAudioAttributes(audio);
// No video attributes → audio-only extraction
Encoder encoder = new Encoder();
encoder.encode(
new MultimediaObject(mp4Path.toFile()),
mp3Path.toFile(),
attrs
);
}
public static void main(String[] args) throws Exception {
// 192 kbps is a good default for music; 128 kbps for speech
convert(Path.of("video.mp4"), Path.of("audio.mp3"), 192_000);
System.out.println("Converted to audio.mp3");
}
}
Bitrate guide:
- 128 kbps — speech, podcasts, audiobooks. Acceptable quality.
- 192 kbps — music, good default. Most listeners can't tell from 320.
- 320 kbps — highest MP3 quality. Use for archiving or audiophile output.
JAVE2 extracts all audio by default. If the MP4 has multiple audio tracks, you can select one by passing attrs.setAudioStream(streamIndex).
FFmpeg via ProcessBuilder (if FFmpeg is already installed)
If FFmpeg is available as a system binary, calling it directly via ProcessBuilder avoids bundling native JARs:
import java.io.IOException;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
public class Mp4ToMp3FFmpeg {
public static void convert(Path mp4Path, Path mp3Path, int bitrateKbps)
throws IOException, InterruptedException {
ProcessBuilder pb = new ProcessBuilder(
"ffmpeg",
"-i", mp4Path.toAbsolutePath().toString(),
"-vn", // No video
"-acodec", "libmp3lame",
"-ab", bitrateKbps + "k",
"-ar", "44100", // Sample rate
"-y", // Overwrite output without prompt
mp3Path.toAbsolutePath().toString()
);
pb.redirectErrorStream(true);
Process process = pb.start();
String output = new String(process.getInputStream().readAllBytes());
boolean finished = process.waitFor(120, TimeUnit.SECONDS);
if (!finished) {
process.destroyForcibly();
throw new IOException("FFmpeg timed out");
}
if (process.exitValue() != 0) {
throw new IOException("FFmpeg error: " + output);
}
}
public static void main(String[] args) throws Exception {
convert(Path.of("video.mp4"), Path.of("audio.mp3"), 192);
}
}
Method 2: ChangeThisFile API (Java 11 HttpClient, no SDK)
Send the MP4 as a multipart POST. The API extracts audio and returns an MP3. Free tier covers 1,000 conversions/month.
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class Mp4ToMp3Api {
private static final String API_KEY = "ctf_sk_your_key_here";
private static final String API_URL = "https://changethisfile.com/v1/convert";
private static final HttpClient HTTP = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
public static byte[] convert(Path mp4Path) throws IOException, InterruptedException {
String boundary = "----CTFBoundary" + UUID.randomUUID().toString().replace("-", "");
byte[] fileBytes = Files.readAllBytes(mp4Path);
List<byte[]> parts = new ArrayList<>();
parts.add(("--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"target\"\r\n\r\nmp3\r\n").getBytes(StandardCharsets.UTF_8));
parts.add(("--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"" + mp4Path.getFileName() + "\"\r\n" +
"Content-Type: video/mp4\r\n\r\n").getBytes(StandardCharsets.UTF_8));
parts.add(fileBytes);
parts.add(("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8));
int totalLen = parts.stream().mapToInt(b -> b.length).sum();
byte[] body = new byte[totalLen];
int offset = 0;
for (byte[] part : parts) {
System.arraycopy(part, 0, body, offset, part.length);
offset += part.length;
}
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(API_URL))
.header("Authorization", "Bearer " + API_KEY)
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.timeout(Duration.ofSeconds(120))
.POST(HttpRequest.BodyPublishers.ofByteArray(body))
.build();
HttpResponse<byte[]> response = HTTP.send(request,
HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) {
throw new IOException("API error " + response.statusCode() +
": " + new String(response.body()));
}
return response.body();
}
public static void main(String[] args) throws Exception {
byte[] mp3 = convert(Path.of("video.mp4"));
Files.write(Path.of("audio.mp3"), mp3);
System.out.println("Saved audio.mp3 (" + mp3.length + " bytes)");
}
}
When to use each
| Approach | Best for | Tradeoff |
|---|---|---|
| JAVE2 | No system FFmpeg required, clean Java API, cross-platform | Bundles FFmpeg native binary (~10MB per OS); larger JAR |
| ProcessBuilder + FFmpeg | FFmpeg already installed, full FFmpeg flag control | Requires system FFmpeg; exec-based (process management needed) |
| ChangeThisFile API | Serverless, Lambda, zero native deps | Network latency; 25MB limit on free tier (video files can be large) |
Production tips
- Use CompletableFuture for batch MP4 processing. Wrap each JAVE2 encode in
CompletableFuture.supplyAsync()with a bounded thread pool (2–4 threads) — FFmpeg is CPU-heavy; oversubscribing stalls all conversions. - Stream large files to the API instead of reading into memory. For large MP4s, use
HttpRequest.BodyPublishers.ofInputStream(() -> Files.newInputStream(path))instead ofofByteArray()— this avoids loading the entire file into heap. - Add a timeout on JAVE2 encodes. JAVE2 doesn't expose a built-in timeout. Wrap the encode in a
FuturewithexecutorService.submit()and callfuture.get(120, TimeUnit.SECONDS)to enforce a time limit. - Check for audio stream existence before converting. Some MP4 files contain only video (no audio track). Check with
MultimediaObject.getInfo().getAudio()before calling encode — JAVE2 throws EncoderException on audio-less video if audio attributes are set. - 192 kbps is the sweet spot for music. Most listeners can't distinguish 192 kbps MP3 from 320 kbps in blind tests. Use 128 kbps for speech to minimize file size.
JAVE2 is the most self-contained option for Java — bundle the OS-specific native JAR and you get full FFmpeg power with a clean API. For serverless or minimal-dependency environments, the ChangeThisFile API handles the conversion from a HttpClient POST with no natives in your runtime. Free tier covers 1,000 conversions/month.