Skip to content
Image docs1.0

Runnable example

This Spring Boot 4 example validates and decodes one uploaded image once, then fans the immutable value out to OCR, object detection, and barcode/QR analysis. It demonstrates shared qualification, isolated provider execution, partial results, and a replaceable business policy without choosing a particular ML model. A visitor pass is the reference scenario: text, a face region, and a visitor: QR value are facts that a policy can evaluate together.

Use this workshop when several image analyses must share one guarded decode and still remain independently observable. It is useful for shipping labels, product labels, and intake documents as well as visitor passes. It is a teaching boundary, not a complete service: authentication, retention, malware scanning, and production model governance remain application responsibilities.

The workshop is not published and has no Maven coordinate. Select one bluetape4k-dependencies version for published image modules; run the example from this repository with :spring-boot-image-intelligence-api.

  • Qualification checks the declared media type, compressed size, signature, dimensions, and decoded pixel budget before the single ImmutableImage decode.
  • ImageIntelligenceWorkflow runs OCR, detection, and ZXing lanes through suspendParallelFlow; each lane writes a distinct WorkContext key.
  • AnalysisResult distinguishes Completed, Empty, Unavailable, and Failed. A provider failure is domain data and does not discard successful siblings.
  • The aggregate is COMPLETED when every lane is available or empty, PARTIAL when some results coexist with unavailable/failed lanes, and FAILED when no lane produced an available result.
  • VisitorPassPolicy consumes provider-neutral facts. Replace that policy for a different domain without rewriting qualification or orchestration.

The default profile needs no external OCR or ML service. Start it with:

Terminal window
./gradlew :spring-boot-image-intelligence-api:bootRun

For deterministic OCR and detection fixtures while keeping real ZXing decoding, use the demo profile:

Terminal window
./gradlew :spring-boot-image-intelligence-api:bootRun \
--args='--spring.profiles.active=demo'

Then submit a PNG, JPEG, or WebP image:

Terminal window
curl -X POST \
-F "file=@visitor-pass.png;type=image/png" \
http://localhost:8080/api/images/intelligence

The optional native-ocr profile requires host Tesseract and traineddata. Do not activate it together with demo; provider ownership is deliberately exclusive.

TaskExample boundary
Submit an imagePOST /api/images/intelligence, multipart field file
Guard inputImageUploadQualifier checks type, bytes, signature, dimensions, and pixels
Run analysesImageIntelligenceWorkflow coordinates OCR, detection, and ZXing
Preserve partial resultsAnalysisResult plus ImageIntelligenceAggregator
Apply business rulesVisitorPassPolicy maps facts to ALLOW or MANUAL_REVIEW

Decode once and share an immutable image; never let each provider reopen the upload. Keep provider timeouts and semaphores separate, and treat Empty (“ran and found nothing”) differently from Failed (“could not verify”). Propagate external CancellationException instead of turning it into a business failure. A native call that ignores interruption may still hold its thread until it returns, so a production deployment can add process isolation and a process-level timeout.

The example combines bluetape4k-images decoding, the OCR contract, the provider-neutral barcode API, and the ZXing provider with Spring Boot 4 and bluetape4k-workflow. It intentionally keeps the detection adapter local so a real model can be introduced without changing the HTTP or aggregate contract.

The release defaults are five MiB compressed input, 8,192 pixels per side, 16,777,216 decoded pixels, and independent provider timeouts/concurrency:

example:
image-intelligence:
max-input-bytes: 5242880
max-input-pixels: 16777216
max-input-side: 8192
ocr-timeout: 3s
detection-timeout: 2s
barcode-timeout: 2s
ocr-concurrency: 1
detection-concurrency: 2
barcode-concurrency: 4
tessdata-path: null

Keep host paths such as tessdata-path in application configuration. Never accept them from an upload request.

  • 400 indicates an empty upload, unsupported media type, signature mismatch, malformed image, or a dimension/pixel qualification failure.
  • 413 indicates that the compressed input limit was exceeded.
  • A provider-level timeout or unavailable native dependency is represented in the response as Failed or Unavailable; successful sibling results remain.
  • Missing workflow keys or unexpected programming errors are workflow failures, not normal provider results.

Responses expose a stable status/reason code and do not log or return the uploaded payload. Inspect provider configuration and sanitized application logs when a lane is unavailable; do not expose tessdata paths or native exception details to callers.

Add authentication, authorization, tenant quotas, rate limiting, request timeouts, antivirus/content-disarm scanning, retention/deletion, encryption, and audit policy before exposing this endpoint to untrusted traffic. Size and pixel limits protect decode memory, while per-provider semaphores protect CPU and native resources. Measure provider latency and partial-result rates rather than treating PARTIAL as an infrastructure crash.

Run the workshop tests with:

Terminal window
./gradlew :spring-boot-image-intelligence-api:test

The release suite covers real ZXing extraction from a generated QR, qualification boundaries, profile ownership, parallel overlap, partial failures, workflow keys, the policy decision table, external cancellation, permit recovery, payload-free logs, and the HTTP error contract. Native OCR smoke testing remains environment-dependent.

  1. Read the immutable-image and OCR module guides, then run this workshop’s fake-provider tests.
  2. Run the demo profile to observe a complete aggregate without native OCR.
  3. Compare the Ktor and Spring OCR workshops to separate transport concerns from provider orchestration.
  4. Replace VisitorPassPolicy with a shipping-label or product-label policy and retain the same qualification and partial-result boundaries.

The example does not provide authentication, persistence, queues, batch processing, preprocessing policy, malware scanning, tenant isolation, retry or circuit-breaker policy, model selection, quality measurement, or drift monitoring. Coroutine cancellation cannot forcibly terminate a non-cooperative native function; use process isolation when a hard native execution bound is a requirement.