Skip to content

feat(image): enable the libvips image engine by default (#35989) - #36885

Queued
wezell wants to merge 5 commits into
mainfrom
issue-35989-enable-libvips-by-default
Queued

feat(image): enable the libvips image engine by default (#35989)#36885
wezell wants to merge 5 commits into
mainfrom
issue-35989-enable-libvips-by-default

Conversation

@wezell

@wezell wezell commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

Flips the libvips image engine on by default: IMAGE_API_USE_LIBVIPS goes from false to true.

This is the follow-up switch for the work merged in:

Nothing else changes — no new filters, no behavioural code, just the default.

Changes

File Change
VipsManager.java Config.getBooleanProperty(USE_LIBVIPS, false)true
dotmarketing-config.properties IMAGE_API_USE_LIBVIPS=falsetrue (comment updated)
image/vips/README.md Default column falsetrue
ImageFilterExporterEngineSelectionTest.java Javadoc no longer claims legacy-is-default (the test sets the flag explicitly in every case, so it is default-independent)

Why this is safe to flip

VipsManager.isEnabled() is flag && isAvailable():

  • No native libvips installedisAvailable() is false, the legacy Java2D engine is used exactly as today. Turning the flag on cannot break an install that lacks the library.
  • Per-operation failure (corrupt image, missing delegate) → IMAGE_API_LIBVIPS_FALLBACK still defaults to true, so the legacy filter runs for that op.
  • Opt-out → set IMAGE_API_USE_LIBVIPS=false, or env DOT_IMAGE_API_USE_LIBVIPS=false (the DOT_ prefix is required for env overrides).

The dotCMS Docker image installs libvips42, so containerized installs get the libvips path; the AVIF encoder plugin is already wired in CI/Docker from #35990.

Rollout note

This changes the engine that serves resized/filtered images by default. Renditions are byte-different from Java2D output (different resampler), so caches regenerate on first request after upgrade — no rendition invalidation is required, but expect a warm-up on first hit per rendition.

Checklist

  • Docs updated (image/vips/README.md, config comment)
  • Translations — n/a
  • Security considered — no new input surface; the flag is read through Config
  • Tests — no new tests; engine selection is already covered by ImageFilterExporterEngineSelectionTest (both engines exercised explicitly)

🤖 Generated with Claude Code

Flips IMAGE_API_USE_LIBVIPS from false to true. The engine still requires
the native libvips library at runtime — VipsManager.isEnabled() ANDs the
flag with isAvailable(), so installs without libvips keep using the legacy
Java2D engine, and IMAGE_API_LIBVIPS_FALLBACK=true still catches per-op
failures.

Set IMAGE_API_USE_LIBVIPS=false (env: DOT_IMAGE_API_USE_LIBVIPS=false) to
force the legacy engine.

Follow-up to #35990 and #36284.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Claude finished @wezell's task in 1m 20s —— View job


Code Review

  • Read the diff
  • Analyze changed files
  • Post review

Reviewed the 5 changed files against origin/main (isolating the PR commits from merged-in noise).

New Issues

  • 🟡 Medium: VipsManager.java:59 — the availability probe now memoizes on catch (Throwable t). That's the right call for the intended LinkageError/ExceptionInInitializerError case (the comment correctly explains why vavr Try couldn't be used — it rethrows fatal LinkageErrors). But it also captures VirtualMachineError (e.g. a transient OutOfMemoryError during first-touch native init) and permanently caches available = false for the JVM's lifetime, silently disabling libvips even where the library is present. Low probability, but the failure is sticky and invisible after startup. Consider narrowing to the linkage/native-load errors you actually expect, or at least not memoizing false for VirtualMachineError. Assumption: first probe can coincide with memory pressure. What to verify: whether a re-probe on transient failure is worth the added complexity vs. accepting the sticky-false risk.

Notes (non-blocking)

  • VipsManager.java:75 / dotmarketing-config.properties:882 / README.md:14 — default flip to true is consistent across code, config, and docs. The flag && isAvailable() gate plus IMAGE_API_LIBVIPS_FALLBACK=true means an install without native libvips is unaffected, as the PR body states. ✅
  • Image.postman_collection.json:222,265 — switching from an exact Content-Length assertion to reading width/height from the PNG IHDR chunk is the correct fix for engine-independent output. Offsets are right (8-byte signature + 4-byte length + 4-byte IHDR type → width at byte 16, height at byte 20), and >>> 0 guards the sign bit. pm.response.stream is the raw response buffer in Newman, so indexing is valid. ✅
  • ImageFilterExporterEngineSelectionTest.java:20 — javadoc-only change; each case sets the flag explicitly so the test remains default-independent. ✅

Overall this is a clean, well-scoped default flip. The one Medium above is non-blocking — worth a look but the fallback design makes the change safe.
· issue-35989-enable-libvips-by-default

@github-actions github-actions Bot added the Area : Backend PR changes Java/Maven backend code label Aug 4, 2026
wezell and others added 3 commits August 5, 2026 08:54
The two Scale Filter tests pinned Content-Length to a byte-exact value
(980852 / 355062) produced by the Java2D engine. libvips uses a
different resampler, so enabling it by default makes those renditions
byte-different (961776 for the 900x500 case) and the Postman Default
suite fails.

Read width/height from the PNG IHDR chunk instead, which is what the
test name claimed to check all along and is independent of the engine
that produced the bytes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
When native libvips is absent, the class initializer of
app.photofox.vipsffm.Vips fails with ExceptionInInitializerError, and
every subsequent touch throws NoClassDefFoundError. Both are
LinkageErrors, and io.vavr's Try classifies LinkageError as fatal
(Try.java isFatal) — it sneaky-throws instead of capturing a Failure.

So VipsManager.isAvailable() never returned false: it propagated the
error to the caller and never cached a result, re-probing (and
re-throwing) on every call. With the engine enabled by default that
turned "no native libvips" from a graceful fallback into a hard
failure for anything resolving through ImageEngine:

  - BinaryExporterServletTest.requestWebpImage -> NoClassDefFoundError
  - FileMetadataAPITest -> MetadataGeneratorImpl.calculateDimensions
    swallows the Throwable and reports 0x0 dimensions

Use a plain try/catch (Throwable) so a LinkageError is captured, false
is cached, and callers get the pure-JVM engine — which is what the
feature-flag contract always claimed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wezell
wezell added this pull request to the merge queue Aug 6, 2026
Any commits made after this event will not be merged.
@mergify

mergify Bot commented Aug 6, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants