Originally published at ffmpeg-micro.com
You need video processing in your Java app. Maybe you're transcoding user uploads in a Spring Boot service, generating thumbnails for a content platform, or converting formats in an enterprise pipeline. You search "ffmpeg java" and find a dozen approaches, half of them outdated.
FFmpeg is the standard for video processing. But integrating it with Java means choosing between shelling out to a binary, using a wrapper library, or skipping the local install entirely. Each approach has real tradeoffs. This post covers all three with working code so you can pick the right one for your project.
Running FFmpeg from Java with ProcessBuilder
The most direct approach. Install FFmpeg on the machine, then call it from Java using ProcessBuilder:
ProcessBuilder pb = new ProcessBuilder(
"ffmpeg",
"-i", "input.mp4",
"-c:v", "libx264",
"-crf", "23",
"-preset", "medium",
"-c:a", "aac",
"-b:a", "128k",
"output.mp4"
);
pb.redirectErrorStream(true);
Process process = pb.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new RuntimeException("FFmpeg failed with exit code " + exitCode);
}
This works. But you own everything: installing FFmpeg on every server and CI machine, parsing stderr for progress info, handling missing codecs, and managing the binary across Linux/Mac/Windows. On Docker, FFmpeg adds 80-200MB to your image. On serverless (Lambda, Cloud Functions), you're stuck with a 250MB package limit and can't install system binaries at all.
Using Jaffree for a Cleaner API
Jaffree is the most capable Java FFmpeg wrapper. It gives you a fluent builder API instead of raw string arrays:
FFmpeg.atPath()
.addInput(UrlInput.fromUrl("input.mp4"))
.addOutput(UrlOutput.toUrl("output.mp4")
.addArguments("-c:v", "libx264")
.addArguments("-crf", "23")
.addArguments("-preset", "medium")
.addArguments("-c:a", "aac")
.addArguments("-b:a", "128k"))
.execute();
Jaffree also supports progress tracking out of the box:
FFmpeg.atPath()
.addInput(UrlInput.fromUrl("input.mp4"))
.addOutput(UrlOutput.toUrl("output.mp4")
.addArguments("-c:v", "libx264")
.addArguments("-crf", "23"))
.setProgressListener(progress ->
System.out.printf("Frame: %d, Time: %s%n",
progress.getFrame(), progress.getTimeMark()))
.execute();
The API is better than raw ProcessBuilder code. But Jaffree still requires FFmpeg installed on the host. It's a wrapper around the CLI binary, not a replacement for it. You still manage installations, updates, and codec availability across environments.
Processing Video with a Cloud API (No FFmpeg Install)
If you don't want FFmpeg on your servers at all, you can offload processing to a cloud API. FFmpeg Micro gives you full FFmpeg capabilities through HTTP requests. Java 11+ includes java.net.http.HttpClient, so you don't even need a third-party HTTP library:
HttpClient client = HttpClient.newHttpClient();
String requestBody = """
{
"inputs": [{"url": "https://example.com/input.mp4"}],
"outputFormat": "mp4",
"preset": {"quality": "high", "resolution": "1080p"}
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ffmpeg-micro.com/v1/transcodes"))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
// {"id":"b5f5a9c0-...","status":"queued","output_format":"mp4",...}
No binary to install. No Docker image bloat. No codec management. You send a video URL, pick your output settings, and get results back.
For advanced operations, pass raw FFmpeg options instead of presets:
String advancedBody = """
{
"inputs": [{"url": "https://example.com/input.mp4"}],
"outputFormat": "webm",
"options": [
{"option": "-c:v", "argument": "libvpx-vp9"},
{"option": "-crf", "argument": "30"},
{"option": "-b:v", "argument": "0"}
]
}
""";
Polling for Completion
Transcode jobs are asynchronous. Poll the status endpoint until the job finishes:
String jobId = // parse id from the create response
String statusUrl = "https://api.ffmpeg-micro.com/v1/transcodes/" + jobId;
HttpRequest statusRequest = HttpRequest.newBuilder()
.uri(URI.create(statusUrl))
.header("Authorization", "Bearer " + apiKey)
.GET()
.build();
String status = "queued";
while ("queued".equals(status) || "processing".equals(status)) {
Thread.sleep(2000);
HttpResponse<String> statusResponse = client.send(statusRequest,
HttpResponse.BodyHandlers.ofString());
// Parse status from JSON response
status = parseStatus(statusResponse.body());
}
Once the status is completed, grab the download URL:
String downloadUrl = "https://api.ffmpeg-micro.com/v1/transcodes/" + jobId + "/download";
HttpRequest downloadRequest = HttpRequest.newBuilder()
.uri(URI.create(downloadUrl))
.header("Authorization", "Bearer " + apiKey)
.GET()
.build();
HttpResponse<String> downloadResponse = client.send(downloadRequest,
HttpResponse.BodyHandlers.ofString());
// Response: {"url": "https://storage.googleapis.com/...signed-url..."}
The signed URL is valid for 10 minutes. Download the file directly from it.
CLI vs. API: Side-by-Side
The same operation (converting an MP4 to 720p) done both ways:
FFmpeg CLI (requires local install):
ffmpeg -i input.mp4 -vf scale=-2:720 -c:v libx264 -crf 23 -c:a copy output.mp4
FFmpeg Micro API (no install):
String body = """
{
"inputs": [{"url": "https://example.com/input.mp4"}],
"outputFormat": "mp4",
"preset": {"quality": "high", "resolution": "720p"}
}
""";
Same result. The API version runs on managed infrastructure that scales automatically. The CLI version runs on whatever machine you installed FFmpeg on.
Common Pitfalls with FFmpeg in Java
ProcessBuilder doesn't capture stderr by default. FFmpeg writes progress and error info to stderr, not stdout. If you only read getInputStream(), you miss all of it. Always call redirectErrorStream(true) or read both streams separately with threads to avoid deadlocks.
Blocking on process output can deadlock. If FFmpeg writes enough data to fill the OS pipe buffer and your Java code isn't reading it, both processes freeze. Always drain the output stream in a separate thread or use redirectErrorStream(true) with a reader loop.
Jaffree needs FFmpeg on the PATH. If FFmpeg isn't on the system PATH, Jaffree throws a confusing IOException. Either pass the path explicitly with FFmpeg.atPath(Paths.get("/usr/local/bin")) or set it in your container's Dockerfile.
Text blocks need Java 13+. The multi-line """ syntax used in the API examples requires Java 13 or later. On older versions, use string concatenation or a JSON library like Jackson to build the request body.
Don't forget to close processes. A leaked Process object holds OS resources. Use try-with-resources or call process.destroy() in a finally block. Jaffree handles this automatically.
Which Approach Should You Use?
ProcessBuilder if you need full control over FFmpeg flags and you're comfortable managing FFmpeg installations across all environments. Good for batch processing on dedicated servers where you control the OS.
Jaffree if you want a cleaner Java API and built-in progress tracking. Still requires FFmpeg on every machine, but the code is easier to maintain than raw ProcessBuilder calls.
A cloud API like FFmpeg Micro if you want zero operational overhead. Best for serverless deployments, microservices, or teams that don't want to manage FFmpeg infrastructure. You get a free API key to start processing video in under 10 minutes.
FAQ
Can I run FFmpeg inside AWS Lambda with Java?
Technically, yes. You can bundle a static FFmpeg binary in your deployment package. But Lambda has a 250MB package limit (unzipped), 512MB /tmp storage, and a 15-minute timeout. For short clips it works. For anything over a few minutes of video, you'll hit limits. A cloud API avoids these constraints entirely.
Is Jaffree still maintained?
Yes. Jaffree is actively maintained on GitHub with regular releases. It supports Java 8+ and wraps both ffmpeg and ffprobe. It's the most mature FFmpeg wrapper for Java.
What about JavaCV for video processing?
JavaCV wraps OpenCV and FFmpeg via JNI bindings. It's built for computer vision tasks (face detection, frame analysis) but heavyweight for simple transcoding. If you just need to convert formats or resize video, ProcessBuilder, Jaffree, or a cloud API are simpler options.
How does the FFmpeg Micro API handle large files?
For files you can't reference by URL, use the upload flow: request a presigned URL, PUT your file directly to cloud storage, confirm the upload, then reference the cloud storage URL in your transcode request. The API supports files up to 2GB.
Do I need to learn FFmpeg syntax to use the API?
No. Presets handle common operations (quality levels, resolution, format conversion) without any FFmpeg knowledge. If you do know FFmpeg, you can pass raw options for full control over codecs, filters, and encoding parameters.
Last verified: 2026-05-25 against FFmpeg 7.x and FFmpeg Micro API v1
Top comments (0)