Scrimage: immutable image manipulation for the JVM, with codecs split into modules
JVM - Java, Kotlin, Scala image processing library
At a glance
- What is it?
- Scrimage is an Apache-2.0 library for image processing on the JVM that keeps images immutable and composable, and it separates the core from the filters, the extra codecs, the perceptual hashing and the WebP support. It suits a Java or Kotlin service that needs thumbnails and format conversion without pulling in a native imaging stack, and the module split is what controls the dependency weight.
- Who is it for?
- Scrimage fits a Java or Kotlin service that needs reliable thumbnail generation and format conversion with a functional style, because the immutability means an image can be passed through a chain of operations without defensive copies and without a caller worrying that a filter mutated what it was given. Skip it if you need the full codec matrix in one dependency, because the extras are separate modules, and skip it if you want GPU acceleration, since this is a CPU library.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Immutability is the whole design
Scrimage is an immutable, functional, and performant JVM library for manipulation of images. Those three adjectives are the specification.
Immutability is the load-bearing one. An image in Scrimage is a value. Applying a filter returns a new image rather than mutating the one you passed in. That is a different default from most imaging APIs, where a BufferedImage is a mutable buffer and a filter is something that draws into it.
The consequence is that the library composes. You can hold an image, pass it down a chain, and know that whoever else holds a reference to it is looking at the same thing you were, and that nothing you do afterwards changes it. In a service where images flow through a pipeline, that removes a class of bug that is notoriously hard to find, because the bug is not in the filter, it is in the fact that a filter three steps ago quietly changed a buffer someone else was still reading.
The stated aim is to provide a simple and concise way to do common image operations, such as creating thumbnails, converting between image formats, resizing to fit a required width and height, applying filters, cropping, and so on. The list is deliberately unglamorous, and that is the point. This is not a research library for image analysis, it is a set of the operations an application actually performs on uploaded pictures.
And it is easy to use from any language on the JVM. The topics list the languages as java, kotlin and scala, and there is a Slack channel on the Kotlin side, which suggests Kotlin is a primary audience even though the library is written in Java. The implementation is in Java, with a Kotlin-friendly API on top.
The library is published to Maven Central under the group and artifact `com.sksamuel.scrimage:scrimage-core`, and the documentation is a microsite at sksamuel.github.io/scrimage rather than a wiki, with the release history on the releases page.
The licence is Apache-2.0, and the last push was on 2026-10-07.
The module split is a dependency decision
The repository is a multi-module Gradle build, and the module names are the most informative thing in it, because each one is a dependency you can choose not to take.
`scrimage-core` is the library itself: the image type, the operations, and the formats that are always there. `scrimage-filters` holds the filter implementations, and it is a separate module because a filter catalogue is a large set of dependencies that a minimal user may not want.
`scrimage-formats-extra` is the module for codecs beyond the core set. The name tells you the intent, the extra formats are available but they are not free, and the effect is that a service that only handles JPEG and PNG does not inherit the transitive dependencies of every other codec.
`scrimage-webp` is split out again, and WebP is the obvious candidate for that because its encoder is a separate native library. `scrimage-hash` is the perceptual hashing module, which is the part you would use for finding duplicate or near-duplicate images, and it is not something every application needs.
Then there are the non-published modules. `scrimage-benchmarks` is the performance harness, `scrimage-examples` and the top level `examples/` directory hold the sample code, `scrimage-tests` is the test suite, and `buildSrc/` holds the Gradle build logic. The `examples/` directory has three subdirectories, `composite/`, `filters/` and `images/`, which map onto the three things you would want to see: composing operations, applying a filter, and working with images.
The build tooling is current. There is a `settings.gradle.kts`, a `gradle.properties`, the `gradlew` and `gradlew.bat` wrappers, a `gradle/` directory, and a `renovate.json` for automated dependency updates. The Gradle Kotlin DSL and Renovate together suggest a project that keeps its build dependencies current without manual effort.
The documentation uses `mkdocs.yml`, and the repository has an `.editorconfig` and a `SECURITY.md`.
So the shape of the library is: one core module you always need, a filters module you probably need, and a set of modules you take when your format or hashing requirements demand them. That is a design decision made by a library author who has had to answer the question of why your application now pulls in a codec library, and it is the question that most often sinks a dependency like this in a large organisation.
What the topic list says about the format coverage
The repository topics are filters, gif, gif-animation, image, java, jpeg, kotlin, png, scala and webp, and read together they describe both what the library handles and how it is meant to be used.
The image formats named are jpeg, png, gif, gif-animation and webp. The presence of gif-animation as a separate topic is the interesting one, because animated GIF handling is a distinct problem from still image handling. A GIF is a sequence of frames with per-frame delays and a disposal method, and decoding one and re-encoding one while preserving the animation is a different code path from a single frame. The fact that it gets its own topic and its own module-level treatment suggests the library treats animation as a first class case rather than a single frame with a repeat count.
The presence of `filters` as a topic is more significant than it looks. A filter library that exposes a large catalogue of effects, from blurring and sharpening to colour adjustments, is a different product from a general imaging library, and it is the part most likely to pull in native code for the operations that are too slow in Java.
And `webp` being a topic alongside the others, with its own module, tells you the WebP support is a real feature rather than a mention.
What is not in the topic list is as informative. There is no topic for machine learning, for object detection, for computer vision analysis, for OCR, or for the sort of feature extraction you would do on an image to understand its content. Scrimage is a manipulation library: it changes images, it does not interpret them. The `scrimage-hash` module computes perceptual hashes, which is a form of comparison rather than understanding, and it lives in its own module for that reason.
That boundary is a sensible one for a library intended to sit in a request handling path of a web application, where the job is to accept an upload and produce a few derivatives of it.
Where the documentation lives and how to start
The README is short, which is unusual and a good sign, because the detail is elsewhere.
The entry point is the microsite, at sksamuel.github.io/scrimage, and the README's instruction is simply to head over to the microsite to begin. The documentation is built with MkDocs, from the `mkdocs.yml` at the top of the repository, and the `docs/` directory holds the sources alongside the logo image the README uses.
The release history is a separate link rather than a section of the README, pointing at the GitHub releases page, so that the project can be used as a dependency and still have a readable front page. A library whose README is three paragraphs and a logo is making a statement: the documentation is the product page, not the repository.
For the build itself, the repository has a `build_test` workflow badge, which is the continuous integration job that compiles and runs the tests. The `settings.gradle.kts` and `buildSrc/` arrangement means the Gradle build logic is shared across the modules rather than duplicated, which is what keeps a multi-module project consistent when the module list changes.
The examples are the other half of the onboarding. There is a top level `examples/` directory with `composite/`, `filters/` and `images/`, and a separate `scrimage-examples` module. The `composite` directory is the one to look at first for this library, because composability is the property that distinguishes it, and the name suggests the example is about combining operations rather than applying one.
The `docs/` directory is also where a project like this keeps its migration notes, and given the module split with `scrimage-formats-extra` and `scrimage-webp` separated from the core, a reader upgrading from an earlier version needs to know whether a codec they used moved modules. That kind of note is exactly what belongs in the documentation site rather than in the README.
The project also maintains a presence on the Kotlin Slack, which tells you something about who is expected to use it. The library is written in Java and published for the JVM, but the discussion happens on the Kotlin side, and the maintainer is reachable there.
Where Scrimage stops, and what to compare it against
The honest boundaries come from the design choices, and they are worth stating before you commit.
The first is that immutable image processing means allocation. Every operation returns a new image. For a thumbnail pipeline that is entirely reasonable, because you are producing a few derivatives and discarding the intermediate. It is a poor fit for a loop that adjusts the same buffer hundreds of times, where a mutable API would allocate once. A functional library is optimised for the case where the intermediate values are not needed, and the cost shows up if you are working on a single image through many small adjustments.
The second is that this is a CPU library with no acceleration path mentioned anywhere. There is no GPU module, no SIMD module and no native acceleration module in the module list. For the operations it does, that may be fine. For a service resizing millions of images, the throughput ceiling is the library's, and the alternative approaches are different in kind rather than better in degree.
The third is that the extra formats and the filters carry their own dependency weight, and a project that ends up needing every codec and every filter has pulled in a lot of code for the privilege of an immutable API.
The alternative approaches are worth naming. The JDK's own ImageIO is the zero-dependency option and handles the common formats, at the cost of a mutable API and no filters. A native imaging library such as ImageMagick or GraphicsMagick through a process call gives you a much larger operation set and a mature filter library, and the cost is an external binary to deploy and a process boundary per operation. And a dedicated Java imaging library built on the same native codecs gives you the operation set without the process boundary, at the cost of a much heavier dependency.
The difference in approach is that Scrimage takes the functional route on a pure JVM, and the question is whether the immutability is worth having in your codebase. In a service where images pass through a pipeline and several components hold references to them, it very likely is. In a script that resizes one image once, it is ceremony.
On maintenance, the project is in good shape. The recent releases are 4.6.6 on 2026-07-09, 4.6.7 on 2026-07-26 and 4.6.8 on 2026-09-10, and the last push was on 2026-10-07. The repository is not archived, the licence is Apache-2.0, and the artifacts are on Maven Central with a snapshot repository for the nightly versions.
Editorial conclusion
Scrimage fits a Java or Kotlin service that needs reliable thumbnail generation and format conversion with a functional style, because the immutability means an image can be passed through a chain of operations without defensive copies and without a caller worrying that a filter mutated what it was given. Skip it if you need the full codec matrix in one dependency, because the extras are separate modules, and skip it if you want GPU acceleration, since this is a CPU library. Before you adopt it, pick the modules you need, since the `scrimage-formats-extra` and `scrimage-webp` modules exist to keep heavyweight codecs out of the core, and note the release state, with version 4.6.8 published on 2026-09-10 and the last push on 2026-10-07.
Frequently asked questions
What is Scrimage?
It is an immutable, functional, and performant JVM library for manipulation of images, providing a simple and concise way to do common operations such as creating thumbnails, converting between image formats, resizing to fit a required width and height, applying filters and cropping, and it is easy to use from any language on the JVM.
Which modules does the Scrimage project have?
There is scrimage-core for the library itself, scrimage-filters for the filter implementations, scrimage-formats-extra for codecs beyond the core set, scrimage-webp for WebP support and scrimage-hash for perceptual hashing, alongside scrimage-benchmarks, scrimage-examples, scrimage-tests and buildSrc.
Which image formats does Scrimage handle?
The repository topics name jpeg, png, gif, gif-animation and webp, with animated GIF handling treated as a distinct case, and WebP support living in its own scrimage-webp module so its native dependency stays out of the core.
How do I get Scrimage in a Java or Kotlin project?
The artifacts are published to Maven Central under com.sksamuel.scrimage:scrimage-core, with a snapshot repository for nightly builds. The project also keeps a Kotlin Slack channel for questions.
What licence is Scrimage under and how current is it?
Apache-2.0, with the last push on 2026-10-07 and version 4.6.8 published on 2026-09-10. The build uses Gradle with a Kotlin DSL and Renovate for dependency updates.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sksamuel-scrimage)Was this useful?