Java Image and Video Libraries
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. See the section bibliography for the reference material consulted while preparing these pages. |
Baseline date: 2026-10-07. Versions below are the latest releases listed on Maven Central on that date. Check each project’s repository for newer releases and for its licence before you adopt it.
Choosing an image library
| Library | Licence | Status | Notes |
|---|---|---|---|
MIT |
Maintained (0.4.21) |
ImageIO-based. Resize, crop, rotate, watermark, with a fluent API. The recommended default for JVM thumbnails. |
|
Apache-2.0 |
Unmaintained: last release 4.2, 2012 |
Legacy only. Do not choose it for new code. |
|
BSD-3-Clause |
Maintained (3.15.3) |
Plugins for ImageIO: JPEG including CMYK, TIFF, PSD and more. WebP is read only. No AVIF or HEIC. |
|
Apache-2.0 |
Check the repository |
ImageIO plugin that writes WebP through native libraries. |
|
Apache-2.0 |
Maintained (4.6.8) |
Immutable image API with filters and a WebP writer ( |
|
Check each repository |
Check each repository |
Bindings to libvips, for the fastest throughput on large images. vips-ffm (1.9.9) uses the Java foreign function API and needs a recent JDK and libvips installed. |
ImageIO on its own reads JPEG, PNG, GIF and BMP and writes the same. WebP, AVIF and HEIC need extra libraries or a native engine such as libvips. When AVIF output is a requirement, libvips is the practical route on the JVM, see image formats and encoding.
Thumbnailator
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.21</version>
</dependency>
import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.geometry.Positions;
import java.io.File;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
// A 2:3 cover thumbnail: scale to fill, crop the overflow from the centre, JPEG at 80.
Thumbnails.of(new File("original.jpg"))
.size(640, 960)
.crop(Positions.CENTER)
.outputFormat("jpg")
.outputQuality(0.8)
.toFile(new File("w640.jpg"));
// A width-bounded variant. Thumbnailator has no "do not enlarge" option, so .width(1280) would upscale
// a smaller original: read the width from the image header first and cap the target at it.
File original = new File("original.jpg");
int sourceWidth;
try (ImageInputStream in = ImageIO.createImageInputStream(original)) {
ImageReader reader = ImageIO.getImageReaders(in).next();
reader.setInput(in);
sourceWidth = reader.getWidth(0); // header width, before EXIF rotation: compare with the height for orientations 5 to 8
reader.dispose();
}
Thumbnails.of(original)
.width(Math.min(1280, sourceWidth))
.toFile(new File("w1280.jpg"));
Thumbnailator applies the EXIF orientation by default. References: Thumbnailator on GitHub and its Javadoc.
TwelveMonkeys and WebP
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.15.3</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-webp</artifactId>
<version>3.15.3</version>
</dependency>
Adding the plugins to the classpath registers them with ImageIO, so ImageIO.read(file) can then read WebP and
unusual JPEGs. They do not write WebP. To write WebP from the JVM, use webp-imageio, Scrimage’s WebP module, or call
libvips or cwebp. Reference: TwelveMonkeys ImageIO.
Scrimage
<dependency>
<groupId>com.sksamuel.scrimage</groupId>
<artifactId>scrimage-core</artifactId>
<version>4.6.8</version>
</dependency>
<dependency>
<groupId>com.sksamuel.scrimage</groupId>
<artifactId>scrimage-webp</artifactId>
<version>4.6.8</version>
</dependency>
import com.sksamuel.scrimage.ImmutableImage;
import com.sksamuel.scrimage.webp.WebpWriter;
import java.io.File;
ImmutableImage image = ImmutableImage.loader().fromFile(new File("original.jpg"));
image.scaleToWidth(640).output(WebpWriter.DEFAULT.withQ(78), new File("w640.webp"));
Reference: Scrimage on GitHub.
libvips from Java
<dependency>
<groupId>app.photofox.vips-ffm</groupId>
<artifactId>vips-ffm-core</artifactId>
<version>1.9.9</version>
</dependency>
vips-ffm needs the libvips shared library on the host and a JDK that supports the foreign function and memory API. Check
the repository README for the exact JDK and libvips requirements, and for the API, which mirrors libvips operations
(thumbnail, heifsave, webpsave). The criteo/JVips binding is an alternative that uses JNI. When neither fits, a
worker that runs the vips command line tool through ProcessBuilder is a robust option.
BlurHash in Java
blurhash-java is the encoder listed in the BlurHash implementations list. Encode
from a downscaled BufferedImage, never from the original.
<dependency>
<groupId>io.trbl</groupId>
<artifactId>blurhash</artifactId>
<version>1.0.0</version>
</dependency>
import io.trbl.blurhash.BlurHash;
import net.coobird.thumbnailator.Thumbnails;
import java.awt.image.BufferedImage;
import java.io.File;
BufferedImage small = Thumbnails.of(new File("original.jpg"))
.size(64, 64) // fits inside 64 x 64 and keeps the aspect ratio
.asBufferedImage();
String hash = BlurHash.encode(small, 4, 3); // 4 x 3 components, 28 characters
Choose the components from the aspect ratio, as explained in the BlurHash page. Reference: hsch/blurhash-java. The Kotlin Multiplatform library vanniktech/blurhash (0.3.0) is an alternative for Kotlin code.
Video from Java
Video from Java means driving FFmpeg. Both libraries below need FFmpeg and FFprobe, either installed on the host or, for JAVE2, bundled.
| Library | Licence | Notes |
|---|---|---|
Apache-2.0 |
Wraps the FFmpeg and FFprobe command line tools. Latest release listed on Maven Central: 2024.08.29. You tell it where FFmpeg is installed. |
|
GPL-3.0 |
Bundles FFmpeg binaries for common platforms, which is convenient and brings GPL obligations and the licences of the bundled codecs. Check them against your distribution model before you use it in a closed-source product. |
Jaffree: transcode, cover and probe
<dependency>
<groupId>com.github.kokorin.jaffree</groupId>
<artifactId>jaffree</artifactId>
<version>2024.08.29</version>
</dependency>
import com.github.kokorin.jaffree.ffmpeg.FFmpeg;
import com.github.kokorin.jaffree.ffmpeg.UrlInput;
import com.github.kokorin.jaffree.ffmpeg.UrlOutput;
import com.github.kokorin.jaffree.ffprobe.FFprobe;
import com.github.kokorin.jaffree.ffprobe.FFprobeResult;
import java.nio.file.Path;
import java.nio.file.Paths;
Path bin = Paths.get("/usr/bin"); // directory that contains ffmpeg and ffprobe
// Probe first, to reject files that are not what the user claimed.
FFprobeResult probe = FFprobe.atPath(bin)
.setShowStreams(true)
.setShowFormat(true) // without it ffprobe returns no format section and getFormat() is null
.setInput("input.mov")
.execute();
double seconds = probe.getFormat().getDuration();
// Transcode to a 720p H.264 rendition with a keyframe every 2 seconds at 24 fps.
FFmpeg.atPath(bin)
.addInput(UrlInput.fromPath(Paths.get("input.mov")))
.addArguments("-vf", "scale=-2:720")
.addArguments("-c:v", "libx264")
.addArguments("-crf", "22")
.addArguments("-g", "48")
.addArguments("-c:a", "aac")
.addOutput(UrlOutput.toPath(Paths.get("720p.mp4")))
.execute();
// Extract a cover frame at 10 percent of the duration.
FFmpeg.atPath(bin)
.addInput(UrlInput.fromPath(Paths.get("input.mov")).setPosition((long) (seconds * 100)))
.addOutput(UrlOutput.toPath(Paths.get("cover.jpg")).setFrameCount(null, 1L))
.execute();
The method names above follow Jaffree’s documented API at the time of writing, so verify them against the README of the version you install. Reference: Jaffree on GitHub.
JAVE2: transcode and cover
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-core</artifactId>
<version>${jave.version}</version>
</dependency>
<dependency>
<groupId>ws.schild</groupId>
<artifactId>jave-nativebin-linux64</artifactId>
<version>${jave.version}</version>
</dependency>
import ws.schild.jave.Encoder;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.encode.AudioAttributes;
import ws.schild.jave.encode.EncodingAttributes;
import ws.schild.jave.encode.VideoAttributes;
import ws.schild.jave.info.VideoSize;
import java.io.File;
AudioAttributes audio = new AudioAttributes();
audio.setCodec("aac");
audio.setBitRate(128000);
VideoAttributes video = new VideoAttributes();
video.setCodec("libx264");
video.setSize(new VideoSize(1280, 720));
EncodingAttributes attrs = new EncodingAttributes();
attrs.setOutputFormat("mp4");
attrs.setAudioAttributes(audio);
attrs.setVideoAttributes(video);
new Encoder().encode(new MultimediaObject(new File("input.mov")), new File("720p.mp4"), attrs);
Set jave.version to the current release from the JAVE2 repository and pick the
native binary artifact for your platform. For fine control of rate and keyframes, passing arguments directly to FFmpeg
(as with Jaffree) is simpler than JAVE2’s attribute objects.
Whichever library you pick, run it in a worker with a time limit, a memory limit and a sandbox, see security and operations. The same code appears in a job pipeline in the Spring Boot and Quarkus pipeline.