Amy Pearson Amy is the product lead for JDeli with expertise in image code, Java, web development, and cloud computing. She focuses on JDeli and has also contributed to JPedal, cloud services, and support. Outside work, she enjoys gaming, F1, and music.

Benchmarking Java Image Libraries encoding: Speed, Quality, and File Size — What Really Matters?

7 min read

TL;DR

When benchmarking Java image libraries, what matters most depends on your use case. Speed is critical for high-throughput services, file size drives storage and bandwidth costs, and quality is non-negotiable when visual output matters. Testing across all three reveals a more honest picture. In this benchmark covering seven formats, JDeli leads overall on speed, quality, and reliability, producing virtually no broken files and handling every format without plugins. ImageIO (with plugins) holds its own on file size and raw speed in certain formats, and Apache delivers solid, consistent quality where it has coverage. But if you need one library that performs well across all three metrics, JDeli is the clear all-rounder. Check out our performance comparisons page if you’re just interested in the numbers


As a developer and product manager, I’m always looking closely at how our library performs, especially compared to other options. I want to know where we’re doing well, where we can improve, and what actually makes a difference in real-world use.

Over the years, I have benchmarked various image encoding and decoding libraries including JDeli and shared the results.

This time, I decided to expand things a bit further and run broader performance benchmarks across the board and dive into performance. Partly because it’s useful, and partly because let’s be honest, it’s fun to see how your work stacks up. I’ll walk you through how I ran the tests and, more importantly, which metrics I believe actually matter.


So… what actually matters?

In a world obsessed with faster and smaller, it’s tempting to look for a single number that tells you which library is “best”.

But as my boss likes to remind me, referencing Mazzeo’s Law:

“The answer to every strategic question is… It depends.”

The right answer depends entirely on what you care about.

If you’re running a high-throughput service, speed might be everything. If you’re trying to reduce bandwidth or storage costs, file size matters more. And if the visual result is critical, then quality is non-negotiable.

There isn’t one winner across every scenario.

So instead of focusing on just one metric, I’ve looked at the three that tend to matter most in the real world:

  • Speed
  • File size
  • Quality

Together, they tell a much more useful story.


How the benchmarks work

To measure these, I used a combination of JMH for performance testing and SSIM for image quality.

Why JMH?

JMH (Java Microbenchmark Harness) is the gold standard for benchmarking Java code. It handles JVM warm-up, avoids common benchmarking pitfalls, and produces reliable, repeatable results.

This is important because naïve benchmarks can be wildly misleading.

Why use this over simpler approaches like System.nanoTime(), custom timers, or basic loop-based benchmarks? The main reason is accuracy. Benchmarking Java code correctly is surprisingly difficult because of JVM optimisations like JIT compilation, garbage collection, and dead code elimination. These can make naïve benchmarks produce misleading and sometimes completely wrong results.

JMH is designed specifically to handle these issues. It includes proper warm-up phases so the JVM can fully optimise the code before measurements are taken, runs multiple iterations to produce stable averages, and uses techniques to prevent the JVM from optimising away the code being tested. It also helps isolate the benchmark from other system activity as much as possible.

In short, JMH gives you confidence that you’re measuring the actual performance of your code, not the side effects of how the JVM happens to be behaving at that moment.


Why SSIM instead of VIF, PSNR or MSE?

For image quality, I’ve used the Structural Similarity Index (SSIM) rather than relying on metrics like PSNR (Peak Signal-to-Noise Ratio) or MSE (Mean Squared Error), and instead of going with Visual information fidelity (VIF ) .

The main reason is simple: I care about how the image looks, not just how different it is numerically.

Metrics like PSNR and MSE work by measuring pixel-by-pixel error. That’s useful, but it doesn’t always match what a person actually sees. You can end up with an image that scores worse on paper but looks better to the eye.

SSIM takes a more perceptual approach, looking at things like structure, luminance, and contrast. In practice, it lines up much more closely with how we judge image quality.

VIF is also a strong option and can often outperform SSIM, but it’s significantly more computationally expensive. For this kind of broad benchmarking, that extra cost doesn’t really justify itself.

SSIM hits a good balance: it’s fast enough to run at scale, and accurate enough to reflect real-world visual quality.


Why not use multiple Quality metrics together?

Many benchmarks include SSIM alongside PSNR and MSE, and there’s nothing inherently wrong with that approach.

However, in practice, these additional metrics often don’t change the overall conclusions — they just add more numbers to interpret.

For this benchmark, the goal is to compare practical performance in realistic scenarios, not to perform academic analysis of compression error.

SSIM provides a strong, reliable indicator of perceptual quality on its own, and adding additional error-based metrics wouldn’t materially change the decisions most developers would make based on the results.

It also keeps the results simpler and easier to interpret.

If there’s a visible quality difference, SSIM will reflect it. If there isn’t, that’s usually what matters most.


Benchmark Methodology

Before getting into the results, it’s worth explaining how the benchmarks were run. Performance numbers without context can be misleading, and small differences in setup can produce very different outcomes.

The goal here wasn’t to create a synthetic “best case” scenario, but to measure performance in a way that reflects how image libraries are actually used in real systems.


Test Environment

All benchmarks were run using the following setup:

  • CPU: M1
  • RAM: 16GB
  • OS: MacOS Tahoe 26.3.1
  • Java version: 17
  • JDeli version: 2026.04
  • Comparison libraries: ImageIO(including plugins: twelvemonkeys, JAI, darkXanther for webp), Apache.

Each test was run on an otherwise idle system to minimise interference from background processes.


Test Images

The image set used for testing is the PngSuite test-suite

Using a varied dataset helps ensure the results reflect real-world usage rather than favouring a specific optimisation. I would usually try to stick to images that work for all libraries I'll be comparing but this time I have chosen this corpus because it includes a lot of varied images that cover every aspect that should be supported and will report on if they can be handled as part of the benchmarking.


Ensuring Fair Comparisons

Where possible, equivalent settings were used across libraries to ensure fair comparisons.

However, this is one of the challenges of benchmarking image encoding libraries — different libraries expose different configuration options, and not all settings map perfectly.

The goal was to use realistic, production-style configurations rather than artificially tuning one library to outperform others.


Speed: The metric everyone looks at first

Let’s start with speed — because nobody wants slow image processing.

Speed benchmarks were run using JMH (Java Microbenchmark Harness).

Each benchmark was run across multiple iterations, and the reported results reflect the average performance after the JVM had fully stabilised.

BMP

Mode: throughput
Count 25
Units: ops/s

BenchMarkScoreError
Apache10.684± 0.027
ImageIO5.288± 0.484
JDeli17.223± 0.064

GIF

Mode: throughput
Count 25
Units: ops/s

BenchMarkScoreError
Apache1.705± 0.251
ImageIO4.713± 0.025
JDeli0.538± 0.003

JPEG

Mode: throughput
Count 25
Units: ops/s

BenchMarkScoreError
ImageIO13.345± 0.049
ImageIO_high10.049± 0.033
ImageIO_low13.989± 0.040
JDeli28.073± 0.513
JDeli_high15.778± 0.126
JDeli_low29.451± 0.491

JPEG 2000

Mode: throughput
Count 25
Units: ops/s

BenchMarkScoreError
ImageIO2.306± 0.007
JDeli_JP26.226± 0.023
JDeli_JPX6.228± 0.020

PNG

Mode: throughput
Count 25
Units: ops/s

BenchMarkScoreError
Apache5.1005.100 ± 0.018
ImageIO4.819± 0.010
ImageIO_fast5.597± 0.014
ImageIO_max_comp3.512± 0.008
JDeli_COMPRESS5.402± 0.027
JDeli_FAST13.061± 0.026
JDeli_QUANT1.192± 0.001
JDeli_UNCOMPRESS12.985± 0.088

TIFF

Mode: throughput
Count 25
Units: ops/s

BenchMarkScoreError
Apache4.140± 0.070
ImageIO_Deflate4.533± 0.054
ImageIO_JPEG7.788± 0.084
ImageIO_LZW4.265± 0.043
ImageIO_uncompressed9.213± 0.029
JDeli_LZW9.473± 0.027
JDeli_better_comp5.484± 0.007
JDeli_better_speed13.116± 0.026
JDeli_deflate6.361± 0.017
JDeli_jpeg25.160± 1.732
JDeli_uncompressed99.850± 3.051

WEBP

Mode: throughput
Count 25
Units: ops/s

BenchMarkScoreError
ImageIO6.508± 0.044
JDeli_lossless4.632± 0.049
JDeli_lossy3.659± 0.034

Quality: How good does the output actually look?

Next, let’s look at image quality. For this I used the Structural Similarity Index (SSIM).

The purpose here was to measure perceived visual quality, rather than purely mathematical differences.

BMP

BenchmarkScorezero length/broken filessimilarity of score
Apache1.00Identical or virtually identical
ImageIO0.443066895772027230Very different
JDeli0.96841729449442140Very similar (high quality)

GIF

BenchmarkScorezero length/broken filessimilarity of score
Apache0.87850906148160080Similar (noticeable but minor differences)
ImageIO0.187693077412029734Very different
JDeli0.82847920721701220Moderately similar (visible differences)

JPEG

BenchmarkScorezero length/broken filessimilarity of score
ImageIO0.984340892468825636Very similar (high quality)
ImageIO_high0.995525255031520236Identical or virtually identical
ImageIO_low0.75396328042296336Moderately similar (visible differences)
JDeli0.96352785661298120Very similar (high quality)
JDeli_high0.98248533174599520Very similar (high quality)
JDeli_low0.73084547601752990Moderately similar (visible differences)

JPEG2000

BenchmarkScorezero length/broken filessimilarity of score
ImageIO0.96596358780775590Very similar (high quality)
JDeli_jp20.88171870060167710Similar (noticeable but minor differences)
JDeli_jpx0.88171870060167710Similar (noticeable but minor differences)

PNG

BenchmarkScorezero length/broken filessimilarity of score
Apache0.9672815807189860Very similar (high quality)
ImageIO1.00Identical or virtually identical
ImageIO_fast1.00Identical or virtually identical
ImageIO_max_comp1.00Identical or virtually identical
JDeli0.99852272876969260Identical or virtually
JDeli_fast1.00Identical or virtually
JDeli_compress1.00Identical or virtually identical
JDeli_uncompressed1.00Identical or virtually identical
JDeli_quant0.99261364384846260Identical or virtually

TIFF

BenchmarkScorezero length/broken filessimilarity of score
Apache1.01Identical or virtually identical
ImageIO_deflate1.01Identical or virtually identical
ImageIO_jpeg0.9964492586342384114Identical or virtually identical
ImageIO_lzw1.01Identical or virtually identical
ImageIO_uncompress1.01Identical or virtually identical
JDeli_better_speed1.01Identical or virtually identical
JDeli_deflate1.01Identical or virtually identical
JDeli_jpeg0.87208265993758271Similar (noticeable but minor differences)
JDeli_lzw1.01Identical or virtually identical
JDeli_uncompressed1.01Identical or virtually identical
JDeli_better_comp1.01Identical or virtually identical

WEBP

BenchmarkScorezero length/broken filessimilarity of score
ImageIO0.976482913339194528Very similar (high quality)
JDeli_lossy0.95392621551990790Very similar (high quality)
JDeli_lossless0.96841729449442122Very similar (high quality)

File Size: Smaller isn’t always better — but it helps

Finally, let’s look at file size. This was measured by comparing the output size of encoded images using equivalent settings across each library.

This reflects how efficiently each encoder compresses image data, which directly impacts:

  • Storage requirements
  • Network transfer time
  • Application performance at scale

BMP

BenchmarkSize (in bytes)Zero length or poor output
Apache4,989,1920
ImageIO4,960,61630
JDeli5,059,9240

GIF

BenchmarkSize (in bytes)Zero length or poor output
Apache604,1670
ImageIO413,8054
JDeli739,2800

JPEG

BenchmarkSize (in bytes)Zero length or poor output
ImageIO205,92536
ImageIO_high944,77936
ImageIO_low113,02236
JDeli237,6320
JDeli_high1,520,6090
JDeli_low135,3720

JPEG2000

BenchmarkSizeZero length or poor output
ImageIO1,453,6740
JDeli_jp2295,5850
JDeli_jpx283,9650

PNG

BenchmarkSizeZero length or poor output
Apache2,741,6660
ImageIO2,768,1190
ImageIO_fast2,877,1920
ImageIO_max_comp2,749,3230
JDeli2,749,0380
JDeli_compress2,749,0380
JDeli_fast2,876,8170
JDeli_quant538,8980
JDeli_uncompress2,876,8170

TIFF

BenchmarkSizeZero length or poor output
Apache1,917,3481
ImageIO_deflate2,911,0521
ImageIO_jpeg911,415114
ImageIO_lzw4,074,4301
ImageIO_uncompressed5,077,9361
JDeli_better_comp2,786,0741
JDeli_better_speed2,912,9861
JDeli_deflate2,787,0041
JDeli_jpeg274,0361
JDeli_lzw4,065,6641
JDeli_uncompressed5,075,8761

WEBP

BenchmarkSizeZero length or poor output
ImageIO80,43028
JDeli_lossless1,542,8342
JDeli_lossy90,9560

Summary: How does JDeli perform?

LibraryFormat coverageSpeedQualityAverage SimilarityAverage Size (in MB)Average amount of Zero length or broken files (out of the 166)
Apache4/75.40725 ± 0.09150.961447660550147Very similar (high quality)2.444356203079221
ImageIO7/7 (with plugins)6.85178571428571 ± 0.06769230769230770.878820368634897Similar (noticeable but minor differences)2.0123698370797318
JDeli7/715.9943157894737 ± 0.3328947368421050.951211619054583Very similar (high quality)1.888199090957641

So when we look into the individual tests each library has their strengths for instance: Apache and JDeli are both good for reliably writing to formats correctly and not writing out zero length or broken files, and ImageIO does well on some speed and output size. But, as you can see from the summary, if you are looking for a java image library that can do it all JDeli is a clear winner!


Over to you

These are my results, but benchmarks are always influenced by real-world usage. Download JDeli and try it yourself.

I’d genuinely love to hear how it went for you! Let me know in the comments.

What did you find when testing? Did your results match mine, or were they different?



Are you a Java Developer working with Image files?

Amy Pearson Amy is the product lead for JDeli with expertise in image code, Java, web development, and cloud computing. She focuses on JDeli and has also contributed to JPedal, cloud services, and support. Outside work, she enjoys gaming, F1, and music.