SVG is a vector format — converting it to PNG means rasterizing: rendering the mathematical paths and shapes to a fixed-size pixel grid. Apache Batik is the canonical Java SVG library, developed under the Apache umbrella and supporting SVG 1.1 in full. It's the same engine used by the Apache XML Graphics project for PDF/PS rendering. For deployments where you can't carry Batik's dependency tree (~20MB), the ChangeThisFile API rasterizes SVG from a HttpClient POST.

Method 1: Apache Batik (SVG 1.1, full spec support)

Apache Batik's PNGTranscoder rasterizes SVG to PNG at any resolution. Apache 2.0 license.

<dependency>
  <groupId>org.apache.xmlgraphics</groupId>
  <artifactId>batik-all</artifactId>
  <version>1.18</version>
</dependency>
<!-- Batik requires xml-apis-ext for some SVG features -->
<dependency>
  <groupId>xml-apis</groupId>
  <artifactId>xml-apis-ext</artifactId>
  <version>1.3.04</version>
</dependency>
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Path;

public class SvgToPng {

    /**
     * Rasterize SVG to PNG.
     * @param width  Output width in pixels. Pass -1 to use SVG's natural width.
     * @param height Output height in pixels. Pass -1 to use SVG's natural height.
     */
    public static void convert(Path svgPath, Path pngPath, int width, int height)
            throws Exception {

        PNGTranscoder transcoder = new PNGTranscoder();

        // Set output dimensions (optional — defaults to SVG viewBox size)
        if (width > 0) {
            transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) width);
        }
        if (height > 0) {
            transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) height);
        }

        try (FileInputStream fis = new FileInputStream(svgPath.toFile());
             OutputStream out = new FileOutputStream(pngPath.toFile())) {

            TranscoderInput input = new TranscoderInput(fis);
            TranscoderOutput output = new TranscoderOutput(out);
            transcoder.transcode(input, output);
        }
    }

    public static void main(String[] args) throws Exception {
        // Render SVG at 512×512 px
        convert(Path.of("icon.svg"), Path.of("icon.png"), 512, 512);
        System.out.println("Converted to icon.png");

        // Render at natural SVG dimensions
        convert(Path.of("logo.svg"), Path.of("logo.png"), -1, -1);
    }
}

Batik supports the full SVG 1.1 spec: gradients, filters, masks, clip paths, embedded fonts, and external references. If only width is specified, height is computed proportionally from the viewBox aspect ratio.

Transparent PNG background: Batik produces transparent-background PNGs by default when the SVG has no background fill. If you need a white background, set it in the SVG: <rect width="100%" height="100%" fill="white"/>.

Rasterize from SVG string (in-memory)

When the SVG content is already in memory (from a template or database), use a StringReader instead of a file:

import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;

import java.io.ByteArrayOutputStream;
import java.io.StringReader;

public class SvgToPngInMemory {

    public static byte[] convert(String svgContent, int width) throws Exception {
        PNGTranscoder transcoder = new PNGTranscoder();
        transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) width);

        TranscoderInput input = new TranscoderInput(new StringReader(svgContent));
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        transcoder.transcode(input, new TranscoderOutput(bos));
        return bos.toByteArray();
    }
}

Method 2: ChangeThisFile API (Java 11 HttpClient, no SDK)

POST the SVG as multipart. The API rasterizes server-side. Free tier covers 1,000 conversions/month.

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class SvgToPngApi {

    private static final String API_KEY = "ctf_sk_your_key_here";
    private static final String API_URL = "https://changethisfile.com/v1/convert";
    private static final HttpClient HTTP = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(10))
        .build();

    public static byte[] convert(Path svgPath) throws IOException, InterruptedException {
        String boundary = "----CTFBoundary" + UUID.randomUUID().toString().replace("-", "");
        byte[] fileBytes = Files.readAllBytes(svgPath);

        List<byte[]> parts = new ArrayList<>();
        parts.add(("--" + boundary + "\r\n" +
            "Content-Disposition: form-data; name=\"target\"\r\n\r\npng\r\n").getBytes(StandardCharsets.UTF_8));
        parts.add(("--" + boundary + "\r\n" +
            "Content-Disposition: form-data; name=\"file\"; filename=\"" + svgPath.getFileName() + "\"\r\n" +
            "Content-Type: image/svg+xml\r\n\r\n").getBytes(StandardCharsets.UTF_8));
        parts.add(fileBytes);
        parts.add(("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8));

        int totalLen = parts.stream().mapToInt(b -> b.length).sum();
        byte[] body = new byte[totalLen];
        int offset = 0;
        for (byte[] part : parts) {
            System.arraycopy(part, 0, body, offset, part.length);
            offset += part.length;
        }

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(API_URL))
            .header("Authorization", "Bearer " + API_KEY)
            .header("Content-Type", "multipart/form-data; boundary=" + boundary)
            .timeout(Duration.ofSeconds(30))
            .POST(HttpRequest.BodyPublishers.ofByteArray(body))
            .build();

        HttpResponse<byte[]> response = HTTP.send(request,
            HttpResponse.BodyHandlers.ofByteArray());

        if (response.statusCode() != 200) {
            throw new IOException("API error " + response.statusCode() +
                ": " + new String(response.body()));
        }
        return response.body();
    }

    public static void main(String[] args) throws Exception {
        byte[] png = convert(Path.of("icon.svg"));
        Files.write(Path.of("icon.png"), png);
        System.out.println("Saved icon.png (" + png.length + " bytes)");
    }
}

When to use each

ApproachBest forTradeoff
Apache BatikFull SVG 1.1 support, in-memory rendering, custom dimensions~20MB dependency tree; relatively slow startup
ChangeThisFile APIZero deps, Lambda/Cloud Run, quick one-off conversionsNetwork latency; 25MB limit on free tier

Production tips

  • Use CompletableFuture for batch SVG rasterization. Batik's PNGTranscoder is thread-safe — create independent transcoder instances per thread and parallelize across a fixed thread pool.
  • Specify output width explicitly for consistent results. SVGs without a viewBox or fixed dimensions rasterize at 72 DPI by default (often too small). Always pass KEY_WIDTH to get a predictable output size.
  • For 2x/3x retina PNGs, double the target width. If you need a 256px icon, render at 512px and let the browser downscale. Batik rasterizes at any resolution without quality loss.
  • Reuse HttpClient for API calls. One static instance per JVM — it manages connection pooling internally.
  • Watch for external resource references in SVG. SVGs that reference external fonts (xlink:href to a web font) may render differently in Batik vs a browser. Embed fonts inline using base64 data URIs for consistent output.

Apache Batik is the production-grade choice for SVG-to-PNG in Java — full SVG 1.1 support, Apache 2.0 license, and in-memory rendering from strings or files. For Lambda/Cloud Run where you want no extra JARs, the ChangeThisFile API rasterizes from a HttpClient POST. Free tier covers 1,000 conversions/month.