Java’s built-in javax.imageio.ImageIO has been the default image library since J2SE 1.4. It reads and writes JPEG, PNG, BMP, GIF, and TIFF (from JDK 9 onward). For prototypes and internal tools, that coverage works. But production systems that process user-uploaded images, medical imaging data, or Apple device photos will eventually need AVIF, HEIC, JPEG 2000, WebP support, and that’s where ImageIO stops.
This post compares JDeli and Java ImageIO across speed, output quality, memory behaviour, format coverage, stability, and API design. The benchmark data comes from JMH (Java Microbenchmark Harness) tests published on the JDeli performance comparison docs (last updated May 2026).
The test code is on GitHub, runs with the trial jar, and you can reproduce every number on your own hardware.
We built JDeli because ImageIO and JAI were breaking our PDF rendering pipeline in ways we couldn’t patch from the outside. This is a review of where each library wins and where it falls short.
What Is Java ImageIO?
javax.imageio.ImageIO is the standard Java API for reading and writing raster images. It ships with every JDK, so there’s nothing to install. You call ImageIO.read(file) to decode an image into a BufferedImage, and ImageIO.write(image, format, file) to encode it.
ImageIO uses a plugin-based service provider architecture. Third-party plugins can register additional format readers and writers. The most common plugin historically was JAI (Java Advanced Imaging), which added JPEG 2000 and TIFF support. It ships with known bugs, runs slowly, and consumes excessive memory on large files.
What Is JDeli?
JDeli is a commercial pure-Java image library built by IDRsolutions. It writes formats including AVIF, BMP, GIF, HEIC, JPEG, JPEG 2000, PNG, TIFF, WebP and can read DICOM, EMF, JPEG XL, PSD, SGI, WMF. It has no native dependencies, no third-party dependencies, and makes no network calls during operation.
JDeli’s API mirrors ImageIO’s simplicity (JDeli.read(), JDeli.write()) but adds type-safe format enums, custom encoder options, chained image processing, and one-step format conversion.
It also includes an ImageIO plugin that routes existing ImageIO.read() and ImageIO.write() calls through JDeli’s decoders, which means you can add JDeli to a legacy codebase without changing a single line of application code.
JMH Speed Benchmarks
All figures below use JMH in throughput mode (operations per second), where the higher the result, the better it performs. A standard set of test images was used across all libraries, with 25 measurement iterations per benchmark. Apache Commons Imaging is included where it supports the format.
Reading (Decoding) Throughput
| Format | ImageIO (ops/s) | ImageIO + JAI (ops/s) | JDeli (ops/s) | Fastest |
|---|---|---|---|---|
| BMP | 56.38 | — | 171.24 | JDeli (3.0x) |
| GIF | 1.65 | — | 5.36 | JDeli (3.2x) |
| JPEG | 0.143 | — | 0.162 | JDeli (1.1x) |
| JPEG 2000 | — | 48.97 | 123.79 | JDeli (2.5x) |
| PNG | 296.92 | — | 2160.50 | JDeli (7.3x) |
| TIFF | 7.98 | — | 11.30 | JDeli (1.4x) |
JDeli wins across every read benchmark. The PNG read improvement is the most dramatic at 7.3x faster than ImageIO.
JPEG reads are close between ImageIO and JDeli (0.143 vs 0.162 ops/s). For JPEG-heavy workloads where read speed is the bottleneck, the difference is small enough that other factors (format support, stability, memory behaviour) should drive the decision.
Writing (Encoding) Throughput
Writing benchmarks are more nuanced because each library offers different compression modes. The table below compares equivalent default settings:
| Format | ImageIO (ops/s) | ImageIO + JAI (ops/s) | JDeli (ops/s) | Fastest |
|---|---|---|---|---|
| BMP | 5.29 | — | 17.22 | JDeli (3.3x) |
| GIF | 4.71 | — | 0.54 | ImageIO (8.7x) |
| JPEG | 13.35 | — | 28.07 | JDeli (2.1x) |
| JPEG 2000 | — | 2.31 | 6.23 | JDeli (2.7x) |
| PNG | 4.82 | — | 5.38 | JDeli (1.1x) |
| TIFF (Deflate) | 4.53 | — | 6.36 | JDeli (1.4x) |
| TIFF (LZW) | 4.27 | — | 9.47 | JDeli (2.2x) |
ImageIO wins GIF writes. JDeli wins everything else. The JPEG write performance gap is notable: JDeli encodes JPEG at 28.07 ops/s vs ImageIO’s 13.35 ops/s at default quality, a 2.1x improvement.
JDeli also offers specialized compression modes not available in ImageIO. For PNG, JDeli’s fast mode hits 13.06 ops/s (2.7x faster than its own default and nearly 3x faster than ImageIO). For TIFF, JDeli’s speed-optimized mode reaches 13.12 ops/s compared to ImageIO’s best of 9.21 ops/s (uncompressed).
Output Quality (SSIM)
Speed means nothing if the output looks wrong. The benchmark suite measures structural similarity (SSIM) between the original image and the encoded output. SSIM scores range from 0 (completely different) to 1.0 (identical).
| Format | ImageIO SSIM | JDeli SSIM | ImageIO Unreadable Files | JDeli Unreadable Files |
|---|---|---|---|---|
| BMP | 0.443 | 0.968 | 30 / 166 | 0 / 166 |
| JPEG (default) | 0.984 | 0.964 | 36 / 166 | 0 / 166 |
| JPEG (high quality) | 0.996 | 0.982 | 36 / 166 | 0 / 166 |
| PNG | 1.000 | 1.000 | 0 / 166 | 0 / 166 |
| TIFF (Deflate) | 1.000 | 1.000 | 1 / 166 | 1 / 166 |
The SSIM scores are comparable for lossless formats (PNG, TIFF deflate both produce identical output). For JPEG, ImageIO has a slight SSIM advantage at both default and high quality settings, but ImageIO also failed to produce readable output for 36 out of 166 test files.
JDeli produced readable output for all 166. A library that scores 0.984 on the files it can handle but fails on 22% of them is less useful than one that scores 0.964 on every file in the set.
The BMP result is particularly revealing: ImageIO’s BMP writer produced unreadable output for 30 out of 166 test files, and the files it did write had an average SSIM of just 0.443 (very different from the originals). JDeli wrote all 166 files with an average SSIM of 0.968.
Memory and JVM Stability
ImageIO’s JPEG and TIFF decoders rely on native (C/C++) code. That native code allocates memory outside the Java heap, invisible to the garbage collector. In practice this creates two failure modes:
The first is OutOfMemoryError exceptions that don’t respond to -Xmx tuning, because the memory pressure is on the native heap, not the Java heap. Increasing -Xmx won’t help if the native allocator is the one running out of space.
The second is full JVM crashes. When native code corrupts memory or exceeds its allocation, the process dies without a catchable exception. Your application just stops. In a containerized environment, that means the pod restarts and any in-flight requests are lost.
JDeli is 100% pure Java. Every byte of memory it uses lives on the Java heap where the GC can manage it and where standard JVM flags (-Xmx, -Xms, GC tuning) control its behaviour.
For server-side and cloud deployments, this difference matters more than any speed benchmark. A library that’s 20% slower but never crashes your JVM is a better production choice than one that’s faster but occasionally kills your container.
Format Support Comparison
| AVIF | No | No | Read / Write |
| BMP | Read / Write | Read / Write | Read / Write |
| DICOM | No | No | Read |
| EMF / WMF | No | No | Read |
| GIF | Read / Write | Read / Write | Read / Write |
| HEIC | No | No | Read / Write |
| JPEG | Read / Write | Read / Write | Read / Write |
| JPEG 2000 | No | Read (buggy, slow) | Read / Write |
| JPEG XL | No | No | Read |
| No | No | Write | |
| PNG | Read / Write | Read / Write | Read / Write |
| PSD | No | No | Read |
| SGI | No | No | Read |
| TIFF | Read (JDK 9+) | Read / Write | Read / Write |
| WebP | No | No | Read / Write |
ImageIO covers the classic web formats. If your project only processes JPEG, PNG, and GIF, it works. The gaps show up in specific scenarios: mobile applications (Apple devices shoot HEIC by default), next-gen web delivery (WebP and AVIF produce smaller files than JPEG at equivalent quality), medical imaging (DICOM), print and publishing workflows (JPEG 2000 with full write support), and vector graphics embedded in enterprise documents (EMF/WMF).
JAI partially fills the JPEG 2000 gap, but Oracle abandoned JAI years ago.
Image Processing
ImageIO is a read/write library. If you need to resize, rotate, crop, blur, or watermark an image, you’re on your own with Graphics2D and AffineTransform.
JDeli includes a built-in processing API. You define operations on an ImageProcessingOperations instance, chain them in any order, and apply them in a single pass. Operations execute in the order they’re added.
There are two ways to apply operations to a BufferedImage in memory. JDeli.process() keeps processing separate from file I/O:
ImageProcessingOperations operations = new ImageProcessingOperations();
operations.scale(0.5f);
operations.rotate(90);
operations.blur();
BufferedImage result = JDeli.process(operations, image);
You can also chain operations using the builder pattern and call apply() directly:
ImageProcessingOperations operations = new ImageProcessingOperations()
.scale(0.5f)
.rotate(90)
.blur();
BufferedImage result = operations.apply(image);
To apply processing during a file conversion (read, process, and write in one call), pass the operations into JDeli.convert(). This also handles format conversion at the same time:
ImageProcessingOperations operations = new ImageProcessingOperations()
.scale(0.75f);
JDeli.convert(new File("input.tiff"), new File("output.webp"), operations);
JDeli.convert() also accepts InputStream/OutputStream and byte[], so the same pattern works in servlet pipelines or cloud functions where you don’t have file handles.
Available built-in operations include blur, brighten, crop, edge detection, emboss, gaussian blur, invert colors, mirror, resize (to fit, to height, to width), rotate, scale, sharpen, superscale2x, and watermark. You can also implement the ImageOperation interface to define your own:
private class GrayscaleOperation implements ImageOperation {
@Override
public BufferedImage apply(BufferedImage image) {
// custom processing logic here
return image;
}
}
ImageProcessingOperations operations = new ImageProcessingOperations()
.scale(0.5f)
.custom(new GrayscaleOperation());
This is relevant for batch pipelines where you’re converting uploaded images to web-friendly formats and generating thumbnails. With ImageIO, you’d read the file, manipulate the BufferedImage with separate Graphics2D calls, then write it out as a separate step. With JDeli, the read-process-write flow is a single JDeli.convert() call.
Security and Deployment
JDeli makes zero network calls. No telemetry, no license server phone-home, no cloud routing. Images are processed entirely in-process on whatever machine runs the JVM. The library ships as a single jar with no transitive dependencies.
This matters for three deployment scenarios:
- Air-gapped environments (government, defense, financial services) need a definitive “no data leaves the environment” answer. JDeli provides that without caveats.
- Container-based deployments benefit from having no native libraries to install or configure. JDeli runs in any JVM container image. No system library packages, no JNI configuration, no platform-specific binaries.
- Security scans flag unmaintained dependencies. JAI and the older ImageIO native code modules trigger these warnings regularly. JDeli is actively maintained with regular stable releases, so it won’t show up as unsupported software in your vulnerability scanner.
How JDeli Improves on ImageIO’s API Behaviour
If you’re migrating code that was written around ImageIO’s quirks, you’ll notice a few places where JDeli does things differently, and in each case it’s a deliberate improvement:
Explicit Error Reporting (Instead of Silent Nulls)
ImageIO.read() returns null when it can’t find a reader for a format. This means a missing codec shows up as a NullPointerException somewhere downstream, far from the actual cause. JDeli throws an exception immediately with a message that tells you what went wrong.
If your ImageIO code checks for null returns, you can replace those null checks with try-catch blocks for cleaner control flow, or leave them as defensive checks alongside the exception handling.
Default Full Thread Safety
ImageIO’s native-backed readers have had historical thread-safety issues, particularly with JAI plugins. If you’re processing images in parallel and seeing intermittent, hard-to-reproduce failures with ImageIO, switching to JDeli may eliminate them without any changes to your threading logic.
Higher File Compatibility
During benchmark testing, ImageIO’s readers failed to produce readable output for 22-69% of test files in certain formats (36/166 JPEGs, 30/166 BMPs, 114/166 JPEG-compressed TIFFs). JDeli processed the same files successfully.
If you have a corpus of images that occasionally throws exceptions under ImageIO, test them against JDeli before assuming the files are corrupted.
Which Image Library To Choose?
Stick with ImageIO if:
- You only need JPEG, PNG, BMP, and GIF
- You’re building a quick prototype or internal tool where stability under load doesn’t matter
- You can’t add commercial dependencies to your project
Switch to JDeli if:
- You need HEIC, AVIF, WebP, JPEG XL, or JPEG 2000 support
- You’re seeing OutOfMemoryErrors or JVM crashes from image processing
- You need faster JPEG, PNG, or TIFF encoding in batch pipelines
- You’re processing user-uploaded images and need to handle whatever formats arrive
- You need reproducible builds with no native dependencies
- You deploy to air-gapped or high-security environments
- Security scans are flagging JAI or ImageIO native modules as unsupported
As experienced Java developers, we help you work with images in Java and bring over a decade of hands-on experience with many image file formats.
Are you a Java Developer working with Image files?
// Read an image
BufferedImage bufferedImage = JDeli.read(avifImageFile);
// Write an image
JDeli.write(bufferedImage, "avif", outputStreamOrFile);// Read an image
BufferedImage bufferedImage = JDeli.read(dicomImageFile);// Read an image
BufferedImage bufferedImage = JDeli.read(heicImageFile);
// Write an image
JDeli.write(bufferedImage, "heic", outputStreamOrFile);// Read an image
BufferedImage bufferedImage = JDeli.read(jpegImageFile);
// Write an image
JDeli.write(bufferedImage, "jpeg", outputStreamOrFile);
// Read an image
BufferedImage bufferedImage = JDeli.read(jpeg2000ImageFile);
// Write an image
JDeli.write(bufferedImage, "jpx", outputStreamOrFile);
// Write an image
JDeli.write(bufferedImage, "pdf", outputStreamOrFile);
// Read an image
BufferedImage bufferedImage = JDeli.read(pngImageFile);
// Write an image
JDeli.write(bufferedImage, "png", outputStreamOrFile);
// Read an image
BufferedImage bufferedImage = JDeli.read(tiffImageFile);
// Write an image
JDeli.write(bufferedImage, "tiff", outputStreamOrFile);
// Read an image
BufferedImage bufferedImage = JDeli.read(webpImageFile);
// Write an image
JDeli.write(bufferedImage, "webp", outputStreamOrFile);
What is JDeli?
JDeli is a commercial Java Image library that is used to read, write, convert, manipulate and process many different image formats.
Why use JDeli?
To handle many well known formats such as JPEG, PNG, TIFF as well as newer formats like AVIF, HEIC and JPEG XL in java with no calls to any external system or third party library.
What licenses are available?
We have 3 licenses available:
Server for on premises and cloud servers, Distribution for use in a named end user applications, and Custom for more demanding requirements.
How does JDeli compare?
We work hard to make sure JDeli performance is better than or similar to other java image libraries. Check out our benchmarks to see just how well JDeli performs.