Files
spring-ai/image-gen
..

image-gen

Companion code for Image Generation and Vision in Spring AI: ImageModel, Prompt Options and Safe Storage, part of the Spring AI series on ankurm.com. It is the generation half of Multimodal Spring AI: Extract Structured Data from Images.

A prompt goes through our own checks, into ImageModel, and the bytes that come back are verified and written to an S3-compatible bucket under a key we choose.

No image provider was called. The tests use two hand-written fakes -- ScriptedImageModel (an ImageModel that returns what it was told to) and ScriptedModerationModel -- and, for the wire tests, the real OpenAiImageModel against a local mock of /v1/images/generations (FakeImagesServer) that returns a fixed 64x64 PNG whatever the prompt says. Nothing here measures image quality, latency, or whether a real model would refuse a prompt. The storage is real: the ImageStore tests run against a MinIO server in Docker through Testcontainers (the image is chainguard/minio, an upstream MinIO build, because minio/minio and quay.io/minio/minio could not be pulled from the machine these were captured on; the exact version is in output/05-store-minio.txt). Prices in CostModel were copied from the providers' pages on 2026-10-28 and are not checked by any test.

Spring Boot 4.1.2 (parent; GA on Maven Central 2026-10-22)
Spring AI 2.0.2 (spring-ai-model, spring-ai-openai, spring-ai-stability-ai, spring-ai-google-genai-image)
AWS SDK for Java 2.57.5 (s3)
Testcontainers 2.0.5
Java 25
Run scripts/run-all.sh (about 20 s, needs Docker for the MinIO tests)

Source

File What it is
ImageService prompt gate -> ImageModel -> bytes -> verify -> store; one bad image in a batch stores nothing
PromptGate length and control-character checks, then ModerationModel; fails closed when moderation is down
OpenAiOptionsFactory size / quality / n checked against allow-lists, then OpenAiImageOptions with the model set explicitly
ImageSniffer, ImageVerifier, ImagePolicy magic-number sniffing, size cap, pixel cap read from the header, a real decode, allow-list of types
RemoteFetcher downloads a provider-returned URL: https and listed hosts only, no redirects, hard byte cap
ImageStore S3/MinIO writer: server-chosen key, pinned Content-Type, Content-Disposition: attachment, presigned GET
CostModel per-image prices with the date they were read and the page they came from

Captured output (output/)

File Shows
01-api-shape.txt class names, portable vs provider options, what is not there (moderation option, image moderation)
02-wire-requests.txt JSON body sent by the real OpenAiImageModel for six option shapes, including the model reset and the ImagePrompt(List) failure
03-wire-response.txt what ImageResponse carries back (b64 or URL, revised prompt, no usage)
04-wire-refusal.txt a provider 400 content_policy_violation arrives as an SDK exception
05-store-minio.txt, 05b-presigned.txt the object as MinIO holds it; presigned URL behaviour
06-verifier.txt, 07-trailing-bytes.txt, 08-base64-cap.txt twelve inputs against the verifier; the limit of sniffing; base64 refused before decoding
09-fetcher.txt redirect, scheme, host list, endless body
10-keys.txt, 11-naive-vs-pinned.txt owner and id validation; trusting the sender's Content-Type
12-service-happy.txt .. 15-url-results.txt the service end to end; refusals before any paid call; a batch with one bad image; URL results
16-cost.txt the per-image price table

Keys, ports and timestamps differ between runs; everything else is stable.