A comprehensive comparison of four SVG libraries — JairoSVG (Java), EchoSVG (Java), CairoSVG (Python), and JSVG (Java) — to help developers choose the right tool for their SVG rendering needs.
- Overview
- Architecture & Design
- SVG Element Support
- SVG Attributes & Features
- CSS & Styling
- Output Formats
- API & Developer Experience
- Benchmark
- Dependencies & Footprint
- Security
- Visual Rendering Comparison
- Summary
- When to Choose Which
- What About ImageMagick?
- Regenerating
| JairoSVG | EchoSVG | CairoSVG | JSVG | |
|---|---|---|---|---|
| Language | Java 25+ | Java 8+ | Python 3.6+ | Java 8+ |
| Origin | Independent project, API inspired by CairoSVG | Fork of Apache Batik | Original project | Independent project |
| Maintainer | Bruno Borges | css4j project | CourtBouillon / Kozea | Jannis Weis |
| Primary goal | Fast, lightweight SVG → raster/vector conversion | Full-featured SVG toolkit: render, manipulate, and convert | SVG → PNG/PDF/PS conversion | Lightweight SVG renderer for Swing / Java2D |
| License | MIT | Apache-2.0 | LGPL-3.0 | MIT |
| Repository | brunoborges/jairosvg | css4j/echosvg | Kozea/CairoSVG | weisJ/jsvg |
| Current version | 1.0.10 | 2.4 | 2.7+ | 2.0.0 |
| SVG spec target | SVG 1.1 + selective SVG 2 | SVG 1.1 + partial SVG 2 | SVG 1.1 | SVG 1.1 + partial SVG 2 |
| Rendering backend | Java2D | GVT (Batik) → Java2D | Cairo (C library) | Java2D |
| Key strength | Speed (2–30× faster than EchoSVG, on par with JSVG, faster than CairoSVG) | Feature completeness and standard compliance | Native C performance, mature ecosystem | Designed for Swing GUI embedding (IntelliJ, etc.) |
JairoSVG renders SVG through the standard Java2D (Graphics2D / BufferedImage) API. The architecture is intentionally compact:
| Java Class | Role |
|---|---|
JairoSVG |
Public API + Builder |
Surface |
Java2D rendering engine |
Node |
SVG DOM tree |
PathDrawer |
SVG path commands |
ShapeDrawer |
Basic shapes |
TextDrawer |
Text rendering |
Defs |
Gradients, clips, use |
Colors |
Color parsing (170+ named) |
Helpers |
Units, transforms |
CssProcessor |
CSS parsing |
Key technologies:
java.awt.Graphics2D— 2D rendering contextjava.awt.image.BufferedImage— raster image bufferjava.awt.geom.AffineTransform— coordinate transforms- Apache PDFBox 3.0 — PDF output (optional dependency)
Total codebase: ~8,800 lines of Java across 31 source files — a deliberately minimal footprint.
EchoSVG inherits Apache Batik's modular, enterprise-grade architecture built around the GVT (Graphic Vector Toolkit) scene graph model. It is split across dozens of subprojects:
echosvg-dom— SVG DOM implementationechosvg-parser— SVG/XML parsingechosvg-gvt— Graphic Vector Toolkit scene graphechosvg-bridge— DOM-to-GVT bridgeechosvg-css— CSS engine (powered by css4j)echosvg-transcoder— SVG → raster/vector conversionechosvg-svggen— Java2D → SVG generationechosvg-ext— Extensions- …and more
CairoSVG is a Python library built on the Cairo 2D graphics library (C). It uses tinycss2 and cssselect2 for CSS parsing, lxml or ElementTree for XML, and Pillow for raster image handling. The architecture is a set of Python modules (surface.py, shapes.py, path.py, text.py, defs.py, etc.) that JairoSVG mirrors directly.
JSVG is a lightweight Java SVG renderer designed for AWT/Swing applications. It renders SVGs directly onto Graphics2D contexts with minimal memory usage (~50% less than svgSalamander, ~98% less than Batik). Used in production by IntelliJ IDEA, Apache NetBeans, Eclipse SWT, and FlatLaf. JSVG is a renderer, not a converter — it does not produce PNG/PDF output directly; users render to BufferedImage and handle file encoding themselves.
| Aspect | JairoSVG | EchoSVG | CairoSVG | JSVG |
|---|---|---|---|---|
| Core rendering | Java2D (Graphics2D) |
GVT → Java2D | Cairo (C library) | Java2D (Graphics2D) |
| CSS engine | Custom lightweight | css4j (CSS4 support) | tinycss2 + cssselect2 | Built-in (partial) |
| SVG DOM | Read-only Node tree |
Full mutable W3C DOM | ElementTree (read-only) | SVGDocument (pre-processed) |
| Module structure | Single JAR, ~31 classes | 20+ Gradle modules | Single Python package | Single JAR, ~30K LOC |
| Animation engine | None | Full SMIL | None | |
| Scripting | None | Mozilla Rhino (JS) | None | None |
| Filter pipeline | Full (16 primitives) | Full primitives | 3 primitives (feBlend, feFlood, feOffset) | Most primitives (15+ supported) |
| Font handling | Java AWT fonts | AWT + SVG fonts | Cairo font system | Java AWT fonts |
| Extensibility | Minimal (source-level) | High (bridges, handlers) | Minimal (source-level) | DomProcessor + LoaderContext |
| SVG Element | JairoSVG | EchoSVG | CairoSVG | JSVG |
|---|---|---|---|---|
<svg>, <g> |
✅ | ✅ | ✅ | ✅ |
<rect>, <circle>, <ellipse> |
✅ | ✅ | ✅ | ✅ |
<line>, <polyline>, <polygon> |
✅ | ✅ | ✅ | ✅ |
<path> (all commands) |
✅ | ✅ | ✅ | ✅ |
<text>, <tspan> |
✅ | ✅ | ✅ | ✅ |
<textPath> |
✅ | ✅ | ✅ | ✅ |
<image> (raster + nested SVG) |
✅ | ✅ | ✅ (via Pillow) | ✅ |
<use>, <defs> |
✅ | ✅ | ✅ | ✅ |
<symbol> |
✅ | ✅ | ❌ | ✅ |
<linearGradient>, <radialGradient> |
✅ | ✅ | ✅ | ✅ |
<pattern> |
✅ | ✅ | ✅ | |
<clipPath> |
✅ | ✅ | ✅ | ✅ |
<mask> |
✅ | ✅ | ✅ | |
<filter> |
✅ (16 primitives — full SVG 1.1) | ✅ (full primitives) | feBlend, feFlood, feOffset only; no blur/drop-shadow) |
✅ (most primitives; no feImage, feTile, feMorphology, lighting effects) |
<marker> |
✅ | ✅ | ✅ (basic) | ✅ |
<metadata>, <title>, <desc> |
✅ (parsed, not rendered) | ✅ | ❌ (ignored) | ✅ (parsed, not rendered) |
<foreignObject> |
❌ (#15) | ✅ | ❌ | ❌ |
<animate>, <animateTransform>, <animateMotion>, <set> |
❌ (#16) | ✅ (SMIL) | ❌ | animate/animateTransform) |
SVG Fonts (<font>, <glyph>) |
✅ | ✅ | ❌ | ❌ |
<script> |
❌ (#18) | ✅ (Rhino JS) | ❌ | ❌ |
<cursor> |
✅ (parsed, not rendered) | ✅ | ❌ | ❌ |
| Feature | JairoSVG | EchoSVG | CairoSVG | JSVG |
|---|---|---|---|---|
viewBox + preserveAspectRatio |
✅ | ✅ | ✅ | ✅ |
| Transforms (translate, rotate, scale, skewX, skewY, matrix) | ✅ | ✅ | ✅ | ✅ |
Nested <svg> (independent viewports) |
✅ | ✅ | ✅ | ✅ |
| Opacity (element, fill, stroke) | ✅ | ✅ | ✅ | ✅ |
fill-rule (nonzero / evenodd) |
✅ | ✅ | ✅ | ✅ |
| Stroke properties (dasharray, linecap, linejoin) | ✅ | ✅ | ✅ | ✅ |
Gradient spreadMethod (pad / reflect / repeat) |
✅ | ✅ | ✅ | ✅ |
gradientUnits, gradientTransform |
✅ | ✅ | ✅ | ✅ |
patternTransform |
✅ | ✅ | ❌ | ✅ |
fill="url(#id)" references |
✅ | ✅ | ✅ | ✅ |
| Units (px, pt, em, %, cm, mm, in) | ✅ | ✅ | ✅ | ✅ |
font shorthand |
✅ | ✅ | ❌ | ✅ |
font-family, font-size, font-weight |
✅ | ✅ | ✅ (basic) | ✅ |
letter-spacing, text-anchor |
✅ | ✅ | ✅ | ✅ |
text-decoration |
✅ | ✅ | ❌ | ✅ |
| Named colors (170+) | ✅ | ✅ | ✅ | ✅ |
currentColor |
✅ | ✅ | ✅ | ✅ |
rgb() / rgba() / hex colors |
✅ | ✅ | ✅ | ✅ |
hsl() / hsla() |
✅ | ✅ | ❌ | ✅ |
CSS Color Level 4 (oklch, lab, etc.) |
❌ (#23) | ✅ | ❌ | ❌ |
| Feature | JairoSVG | EchoSVG | CairoSVG | JSVG |
|---|---|---|---|---|
Inline style attribute |
✅ | ✅ | ✅ | ✅ |
<style> block (CSS stylesheet) |
✅ | ✅ | ✅ | |
External CSS via <?xml-stylesheet?> |
✅ (requires --unsafe) |
✅ | ✅ (basic) | ❌ |
| Class selectors | ✅ | ✅ | ✅ | ✅ |
| ID selectors | ✅ | ✅ | ✅ | ✅ |
| Descendant / child selectors | ✅ (basic) | ✅ | ✅ (via cssselect2) | |
| Pseudo-classes / pseudo-elements | ✅ (:first-child, :last-child, :nth-child(), :not(), ::first-line, ::first-letter) |
Partial | Partial (via cssselect2) | ❌ |
| CSS Level 4 selectors | ❌ (#26) | ✅ (via css4j) | ❌ | ❌ |
| CSS custom properties (variables) | ✅ | ✅ | ❌ | ❌ |
CSS calc() |
❌ (#28) | ✅ | ❌ | ❌ |
| CSS nesting | ❌ | ✅ | ❌ | ❌ |
@import rules |
✅ | ✅ | ❌ | ❌ |
@supports rules |
❌ (#30) | ✅ | ❌ | ❌ |
EchoSVG integrates the css4j CSS parser, giving it significantly more advanced CSS support. CairoSVG uses tinycss2 + cssselect2, providing solid basic CSS support. JairoSVG's lightweight built-in processor covers the common patterns used in SVG files. JSVG has partial <style> support focused on the CSS patterns most common in SVG icon sets.
| Format | JairoSVG | EchoSVG | CairoSVG | JSVG |
|---|---|---|---|---|
| PNG | ✅ | ✅ | ✅ | ImageIO by user) |
| ✅ (via Apache PDFBox 3.0) | ✅ (via FOP or transcoder) | ✅ (via Cairo) | ❌ | |
| PostScript (PS) | ✅ | ✅ | ✅ | ❌ |
| EPS | ✅ | ❌ | ❌ | ❌ |
| JPEG | ✅ | ✅ | ❌ | ImageIO by user) |
| TIFF | ✅ | ✅ | ❌ | ImageIO by user) |
| In-memory image object | ✅ (BufferedImage) |
✅ (BufferedImage) |
✅ (Cairo surface) | ✅ (BufferedImage) |
Note: JSVG is a renderer, not a converter. It renders SVG to any
Graphics2Dcontext (includingBufferedImage), but does not include built-in file export. Users must handle PNG/JPEG encoding themselves viaImageIO.
byte[] png = JairoSVG.svg2png(svgBytes);
byte[] scaled = JairoSVG.builder()
.fromBytes(svgBytes)
.dpi(150)
.scale(2)
.backgroundColor("#ffffff")
.toPng();
BufferedImage image = JairoSVG.builder()
.fromFile(Path.of("icon.svg"))
.toImage();PNGTranscoder transcoder = new PNGTranscoder();
TranscoderInput input = new TranscoderInput(new FileInputStream("input.svg"));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
transcoder.transcode(input, new TranscoderOutput(baos));
byte[] png = baos.toByteArray();import cairosvg
png = cairosvg.svg2png(bytestring=svg_bytes)
cairosvg.svg2pdf(url="input.svg", write_to="output.pdf")
cairosvg.svg2png(url="input.svg", write_to="output.png",
dpi=150, scale=2, background_color="#ffffff")SVGLoader loader = new SVGLoader();
SVGDocument doc = loader.load(new File("icon.svg").toURI().toURL());
FloatSize size = doc.size();
BufferedImage image = new BufferedImage((int) size.width, (int) size.height,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
doc.render(null, g);
g.dispose();
ImageIO.write(image, "PNG", new File("output.png"));| Feature | JairoSVG | EchoSVG | CairoSVG | JSVG |
|---|---|---|---|---|
| Simple static method API | ✅ | ❌ (transcoder API) | ✅ | ❌ (loader + render) |
| Fluent builder API | ✅ | ❌ | ❌ (keyword args) | ❌ |
| Transcoder API (Batik-style) | ❌ | ✅ | ❌ | ❌ |
| Full SVG DOM (W3C DOM) | ❌ | ✅ | ❌ | ❌ |
| SVG DOM manipulation at runtime | ❌ | ✅ | ❌ | DomProcessor) |
| Swing / GUI viewer component | ❌ | ✅ | ❌ | ✅ (render to Graphics2D) |
| CLI tool | ✅ | ✅ (rasterizer) | ✅ | ❌ |
| DPI control | ✅ | ✅ | ✅ | ❌ (user scales via ViewBox) |
| Scale factor | ✅ | ✅ | ✅ | ✅ (via ViewBox) |
| Background color override | ✅ | ✅ | ✅ | ❌ (user fills manually) |
| Color negation | ✅ | ❌ | ✅ | ❌ |
| Output width / height override | ✅ | ✅ | ✅ | ✅ (via ViewBox) |
| External file access control (XXE) | ✅ (disabled by default) | ✅ (configurable) | ✅ (unsafe flag) |
✅ (via LoaderContext) |
| URL input (http/https) | ✅ | ✅ | ✅ | ✅ (via URL) |
| JBang support | ✅ | ❌ | N/A | ❌ |
| GraalVM Native Image compatible | ✅ (no reflection) | N/A | ✅ (likely) |
SVG → PNG conversion benchmarks comparing all four libraries across 42 SVG test files. Full results in COMPARISON.md.
Highlights (lower is better):
| Test Case | JairoSVG (Java) | EchoSVG (Java) | JSVG (Java) | CairoSVG (Python) |
|---|---|---|---|---|
| Basic shapes | 4.3 ms | 20.9 ms | 4.3 ms | 5.7 ms |
| Gradients | 5.8 ms | 169.5 ms | 5.8 ms | 12.7 ms |
| Filters | 9.8 ms | 45.3 ms | 11.2 ms | 6.3 ms |
| Fe blend modes | 13.6 ms | 37.8 ms | 28.5 ms | 24.2 ms |
| Embedded image | 5.8 ms | 22.3 ms | 7.7 ms | 10.8 ms |
| Localized masks | 19.4 ms | 66.3 ms | 19.7 ms | 25.9 ms |
JairoSVG is 3–29× faster than EchoSVG, on par with JSVG in most scenarios, and faster than CairoSVG in most scenarios.
⚠️ Where CairoSVG appears faster on filters/masks, it is because it silently skips rendering those effects.
JairoSVG also produces compact PNGs — 9% smaller than JSVG and 17.5% smaller than EchoSVG.
See COMPARISON.md for per-test-case benchmark times, file sizes, rendering settings analysis, and instructions to run the benchmark yourself.
| Metric | JairoSVG | EchoSVG | CairoSVG | JSVG |
|---|---|---|---|---|
| Runtime dependencies | 0 (PDFBox optional) | Many (css4j, …) | 5 (cairocffi, tinycss2, cssselect2, defusedxml, Pillow) | 0 |
| Disk footprint | ~130 KB (PNG only), ~2.1 MB with PDFBox | ~5.7 MB (25 JARs) | ~16.6 MB (Python pkgs + Pillow + Cairo C lib) | ~350 KB |
| Artifact size | 1 JAR (~130 KB) + CLI shaded JAR | Many modular JARs | Single Python package | 1 JAR |
| Source files | 31 | 20+ modules | ~10 modules | ~30K LOC |
| Lines of code | ~8,800 | ~200,000+ | ~4,000 | ~30,000 |
| Platform req. | Java 25+ | Java 8+ | Python 3.6+ / Cairo C lib | Java 11+ |
| Build system | Maven | Gradle | pip / setuptools | Gradle |
| Native dependency | None | None | Cairo C library required | None |
| Feature | JairoSVG | EchoSVG | CairoSVG | JSVG |
|---|---|---|---|---|
| XXE protection by default | ✅ | ✅ (configurable) | ✅ (via defusedxml) | ✅ (disabled) |
| External resource loading disabled by default | ✅ | ✅ | ✅ | ✅ (configurable via LoaderContext) |
--unsafe flag to opt-in to external access |
✅ | ✅ | ✅ | ❌ (no CLI) |
| Script execution | ❌ (not supported) | ✅ opt-in (Rhino JS) | ❌ (not supported) | ❌ (not supported) |
SecurityManager integration |
❌ | ✅ | N/A | ❌ |
JairoSVG, CairoSVG, and JSVG share a similar security posture: no scripting support (eliminating script injection), external access blocked or configurable by default. EchoSVG offers more configurability but a larger attack surface.
Side-by-side PNG rendering of 42 SVG test cases across all four libraries. Full gallery in COMPARISON.md.
Sample — 20_fe_blend_modes:
| Input SVG | JairoSVG | EchoSVG | CairoSVG | JSVG |
|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
The test suite covers basic shapes, gradients, paths, text, transforms, strokes, opacity, viewBox, CSS styling, patterns, clip paths, masks, markers, filters (blur, blend, tile, image, color matrix, morphology, turbulence, displacement, lighting, convolve matrix, component transfer), embedded images, SVG fonts, symbols, and more.
See COMPARISON.md for the complete gallery with browser-rendered SVG alongside each library's PNG output.
| Dimension | JairoSVG | EchoSVG | CairoSVG | JSVG |
|---|---|---|---|---|
| Best for | Fast Java SVG conversion | Full SVG toolkit (DOM, scripting, animation) | Python SVG conversion | SVG rendering in Swing/Java2D GUIs |
| SVG spec | SVG 1.1 + selective SVG 2 (static) | SVG 1.1 + partial SVG 2 | SVG 1.1 (static) | SVG 1.1 + partial SVG 2 |
| CSS | Basic + structural pseudo selectors | Advanced (CSS Level 4, css4j) | Basic (via tinycss2) | Good CSS support |
| Performance | 2–30× faster than EchoSVG; on par with JSVG; faster than CairoSVG | Slowest (GVT overhead) | Fast (native C), but skips some filter effects | Fast (lightweight, designed for Swing) |
| API simplicity | One-liner / builder | Transcoder pattern | One-liner functions | SVGLoader + render() |
| Codebase | ~8.8K LOC, 1 dep | ~200K+ LOC, many modules | ~4K LOC, 5 deps | ~30K LOC, minimal deps |
| Animation | ❌ | ✅ | ❌ | ❌ |
| Scripting | ❌ | ✅ | ❌ | ❌ |
| GUI viewer | ❌ | ✅ | ❌ | ✅ (Swing component) |
| License | MIT | Apache-2.0 | LGPL-3.0 | MIT |
Choose JairoSVG when you need:
- True cross-platform, fast, lightweight SVG → PNG/PDF conversion
- Minimal dependencies and small deployment footprint
- A simple, fluent Java API
- Server-side batch rendering where startup time and throughput matter
- A secure default configuration with no scripting surface
- GraalVM Native Image compatibility
Choose EchoSVG when you need:
- A full SVG toolkit with DOM manipulation, scripting, and animation
- Advanced CSS support (Level 4 selectors,
calc(), modern color functions) - A Swing-based SVG viewer component
- Advanced SVG toolkit capabilities beyond conversion (DOM, scripting, animation)
- Compatibility with Java 8+ (JairoSVG requires Java 25+)
foreignObjectsupport or SVG font rendering- Migrating from Apache Batik
Choose CairoSVG when you need:
- SVG conversion in Python
- A mature, widely-used library with a large community
- Integration with Python web frameworks or data pipelines
- No JVM dependency
Choose JSVG when you need:
- A lightweight SVG renderer to embed in a Swing/Java2D GUI application
- A Swing component to display SVG icons or diagrams interactively
- Compatibility with Java 11+ (JairoSVG requires Java 25+)
- MIT-licensed code with minimal dependencies
- An actively maintained renderer optimized for IntelliJ Platform and similar desktop apps
JairoSVG's API design was inspired by CairoSVG's simplicity. Key differences:
| Feature | JairoSVG | CairoSVG |
|---|---|---|
<symbol> element |
✅ | ❌ |
font shorthand |
✅ | ❌ |
| EPS output | ✅ | ❌ |
External CSS <?xml-stylesheet?> |
✅ (requires --unsafe) |
✅ |
Gzip-compressed .svgz input |
✅ | ✅ |
JairoSVG adds features beyond CairoSVG (fluent builder API, BufferedImage output, EPS support, extensive filter primitives) while maintaining a similarly simple API surface.
ImageMagick is a popular command-line image processing toolkit that supports SVG as an input format. However, testing against 19 of the SVG test cases in this comparison revealed that ImageMagick is not a reliable SVG-to-PNG converter. Out of those 19 test cases, ImageMagick failed on 11 (58%) — crashing, producing errors, or generating incorrect output.
| Category | Affected Test Cases | Error |
|---|---|---|
| Font resolution | 04, 08, 09, 12, 13, 14, 17 | unable to read font '' — ImageMagick cannot resolve font families from SVG <style> blocks or font-family attributes |
| Crashes (segfaults) | 05, 18, 19 | malloc: pointer being freed was not allocated / Trace/BPT trap — the built-in MSVG renderer crashes on complex transforms, embedded base64 images, and advanced text |
| Gradient/paint references | 15 | unrecognized color 'fadeLR' — fails to resolve url(#id) gradient references used in masks |
ImageMagick's built-in SVG renderer (MSVG) is a minimal implementation that lacks:
- CSS
<style>parsing — inline stylesheets are largely ignored - Font fallback — if the exact font isn't found on the system, rendering fails entirely
- Gradient/paint server resolution —
url(#id)references in fill/stroke are not reliably resolved - Robust memory management — complex SVG inputs trigger segfaults and aborts
Even when ImageMagick can be configured to delegate SVG rendering to an external library (e.g., librsvg via --delegate), the default installation does not include this, and the built-in renderer is what most users encounter.
Even for the 8 test cases where ImageMagick succeeds, performance is significantly worse than all three dedicated SVG libraries. Each conversion spawns a new magick process, so there is unavoidable process startup overhead. In the Gradients scenario, for example, ImageMagick is roughly 10× slower than JairoSVG.
ImageMagick is an excellent tool for raster image manipulation (resize, crop, compose, format conversion), but its SVG support is too limited for production use. For reliable SVG → PNG conversion, use a dedicated SVG library like JairoSVG, EchoSVG, CairoSVG, or JSVG.
PNG renders + visual comparison — regenerates all PNG renders and rewrites COMPARISON.md:
Prerequisites: JBang, Java 25+, Python 3 with CairoSVG (python3 -m pip install cairosvg), JairoSVG installed in local Maven repo.
./mvnw install -DskipTests
jbang comparison/visual/generate.javaBenchmark data — the per-test-case timing rows in COMPARISON.md are updated automatically after running:
jbang comparison/benchmark/benchmark.javaAll four libraries are complementary: JairoSVG and CairoSVG share DNA and excel as fast conversion engines (Java and Python respectively), EchoSVG is a full SVG runtime, and JSVG is designed for lightweight GUI embedding.



