Use a Spring service to render a fixed-size image, expose it from a controller, and reference that public URL in your Open Graph tags. For pixel-perfect cards, Java2D and BufferedImage are the smallest dependency choice. For layouts that already exist as HTML and CSS, render a Thymeleaf template and pass it through a separate HTML-to-image engine. At higher traffic, generate the file when an article is published and serve an immutable object-storage URL instead of rendering during a crawler request.
Choose the rendering approach first
Spring Boot does not create an Open Graph bitmap by itself. It wires your beans and settings; your application still needs a renderer and an endpoint. Choose the renderer according to the layout you need.
| Approach | Best fit | Advantages | Costs and risks |
|---|---|---|---|
| Java2D | Cards made from text, shapes, logos and a controlled palette | JDK APIs only, deterministic pixels, explicit sizing and fast in-process encoding | You must implement line wrapping, font selection, alignment and fallback glyphs |
| Thymeleaf plus an HTML renderer | Cards that share web markup, CSS tokens or component layouts | Natural HTML/CSS authoring and easier reuse of existing design work | Thymeleaf produces HTML, not PNG; a browser or HTML-to-image runtime is an additional dependency |
| Pre-rendered or asynchronous files | Large sites where the same image is requested by many crawlers | Moves expensive work to publish time and makes delivery cache-friendly | Requires a job, storage and an invalidation/versioning policy |
Whichever option you select, keep rendering in a service. The controller should validate the slug, set HTTP headers and return bytes; the service should own fonts, layout and encoding.
Define the image contract and metadata
Pick one raster size and aspect ratio for your site, then use those exact dimensions in the image and metadata. The Open Graph protocol defines og:image, og:image:secure_url, og:image:type, og:image:width, og:image:height and og:image:alt. The image URL must be absolute, HTTPS and fetchable without authentication by a social crawler.
#1 Best Overall
- Read Before You Buy — No Video Output: These adapters support charging and USB 2.0 data transfer, but cannot transmit video signals. Except for standard USB webcams (which use USB data only), they are not compatible with HDMI/DisplayPort cables, video-capable USB-C hubs, or docking stations with video output.
- Convert USB-A Ports to USB-C: Designed to connect USB-C earphones, cables, flash drives, card readers, and other USB-C accessories to standard USB-A ports. Plug-and-play with no drivers or software required.
- Aluminum Alloy Housing: Built with a sturdy aluminum alloy shell that aids in heat dissipation and protects against daily wear and scratches. Designed to maintain a stable and secure connection.
- Compact & Travel-Friendly: The ultra-compact design allows the adapter to stay plugged into your device without blocking adjacent ports or adding bulk, reducing wear and tear on your original USB ports.
- 12-Month Warranty: Backed by a 12-month manufacturer warranty for peace of mind. Designed to meet strict quality control standards for reliable everyday performance.
For a 1200×630 card, the page containing the article can emit:
<meta property="og:image" th:content="${ogImageUrl}">
<meta property="og:image:secure_url" th:content="${ogImageUrl}">
<meta property="og:image:type" content="image/png">
<meta property="og:image:width" th:content="${ogImageWidth}">
<meta property="og:image:height" th:content="${ogImageHeight}">
<meta property="og:image:alt" th:content="${ogImageAlt}">
og:image:alt describes what is in the image; it is not a caption. Keep it useful for someone who cannot see the bitmap. If you later change dimensions or encoding, update the corresponding metadata and consider a versioned URL so old cached responses cannot be mistaken for the new asset.
Option A: render the card directly with Java2D
1. Add Spring Web
Add spring-boot-starter-web to the application. No image library is required for a basic PNG because BufferedImage, Graphics2D and ImageIO are JDK APIs.
2. Implement a rendering service
The example below creates a 1200×630 PNG, paints a background and accent, wraps a title to a bounded width and draws a small site label. In production, load a known font and logo from classpath resources rather than accepting filesystem or network paths from a request.
Rank #2
- 5-in-1 USB-C Hub: Experience comprehensive connectivity featuring a Power Delivery input, two USB-A 2.0 ports, a USB-A 3.0 port, and an HDMI port. (Note: The USB-C power delivery input port is only for connecting an external wall charger to power your laptop and cannot power peripheral devices.)
- 90W Pass-Through Charging: Achieve optimal charging with 90W pass-through power to your laptop, supported by a total input of 100W, with the hub reserving 10W for operational efficiency. (Note: Wall charger not included.)
- Quick Data Transfers: Accelerate your productivity with rapid data transfers using a high-speed 5Gbps USB 3.0 port and two 480Mbps USB 2.0 ports.
- 4K HDMI Display: Enhance your visual experience with a hub capable of delivering 4K resolution at 30Hz in both mirror and extend modes. Please note that this hub is compatible with MacBook (macOS 12 and newer), Windows 10 and 11, ChromeOS, and laptops equipped with DP Alt Mode and Power Delivery. Note: This device is not compatible with Linux.
- What You Get: Anker USB-C Hub (5-in-1, 4K HDMI), welcome guide, 18-month warranty, and our friendly customer service.
package com.example.og;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
@Service
public class OgImageService {
public static final int WIDTH = 1200;
public static final int HEIGHT = 630;
public BufferedImage render(String title, String siteName) {
String safeTitle = limit(title, 180, "Untitled");
String safeSite = limit(siteName, 80, "Your site");
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
try {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setColor(new Color(17, 24, 39));
g.fillRect(0, 0, WIDTH, HEIGHT);
g.setColor(new Color(59, 130, 246));
g.fillRoundRect(72, 72, 18, HEIGHT - 144, 18, 18);
Font titleFont = new Font("SansSerif", Font.BOLD, 64);
Font siteFont = new Font("SansSerif", Font.PLAIN, 28);
g.setColor(Color.WHITE);
g.setFont(titleFont);
FontMetrics metrics = g.getFontMetrics();
int maxWidth = WIDTH - 190;
List<String> lines = wrap(safeTitle, metrics, maxWidth);
int lineHeight = metrics.getHeight();
int y = 210;
for (String line : lines) {
g.drawString(line, 120, y);
y += lineHeight + 8;
}
g.setFont(siteFont);
g.setColor(new Color(203, 213, 225));
g.drawString(safeSite, 120, HEIGHT - 88);
g.setColor(new Color(148, 163, 184));
g.setStroke(new BasicStroke(2f));
g.drawLine(120, HEIGHT - 125, WIDTH - 120, HEIGHT - 125);
return image;
} finally {
g.dispose();
}
}
private static String limit(String value, int max, String fallback) {
if (value == null || value.isBlank()) return fallback;
String normalized = value.strip();
return normalized.length() <= max ? normalized : normalized.substring(0, max - 1) + "…";
}
private static List<String> wrap(String text, FontMetrics metrics, int maxWidth) {
List<String> lines = new ArrayList<>();
StringBuilder line = new StringBuilder();
for (String word : text.split("\s+")) {
String candidate = line.length() == 0 ? word : line + " " + word;
if (metrics.stringWidth(candidate) <= maxWidth || line.length() == 0) {
line = new StringBuilder(candidate);
} else {
lines.add(line.toString());
line = new StringBuilder(word);
}
if (lines.size() == 4) break;
}
if (line.length() > 0 && lines.size() < 4) lines.add(line.toString());
return lines;
}
}
The title limit and four-line cap are deliberate safeguards. Define your own policy for truncation, missing records and locales, and test the result with the longest supported titles and scripts.
3. Return PNG bytes from a controller
package com.example.og;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Duration;
import org.springframework.http.CacheControl;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OgImageController {
private final OgImageService service;
private final ArticleRepository articles;
public OgImageController(OgImageService service, ArticleRepository articles) {
this.service = service;
this.articles = articles;
}
@GetMapping(value = "/og/{slug}.png", produces = MediaType.IMAGE_PNG_VALUE)
public ResponseEntity<byte[]> og(@PathVariable String slug) throws IOException {
Article article = articles.findPublishedBySlug(slug)
.orElseThrow(() -> new ArticleNotFoundException(slug));
var image = service.render(article.title(), article.siteName());
try (var out = new ByteArrayOutputStream()) {
javax.imageio.ImageIO.write(image, "png", out);
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(Duration.ofHours(1)).cachePublic())
.body(out.toByteArray());
}
}
}
Replace ArticleRepository, Article and the exception with your domain types. The one-hour cache value is only an example. Use a longer public cache for immutable, versioned paths; use invalidation or a short TTL for mutable slugs. If rendering fails, return a normal HTTP error rather than an HTML error page labeled as an image.
Option B: use Thymeleaf for an HTML-based card
When your card needs CSS layout, flexbox-like alignment, rich typography or components shared with the site, add both spring-boot-starter-web and spring-boot-starter-thymeleaf. With the default resolver, templates live under classpath:/templates/ and use the .html suffix unless you configure different values.
Create src/main/resources/templates/og-image.html:
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<style>
html, body { width: 1200px; height: 630px; margin: 0; }
body { font-family: sans-serif; background: #111827; color: white; }
.card { box-sizing: border-box; height: 100%; padding: 90px 120px; }
h1 { font-size: 64px; line-height: 1.12; max-width: 960px; }
.site { margin-top: 120px; color: #cbd5e1; font-size: 28px; }
</style>
</head>
<body>
<main class="card">
<h1 th:text="${title}">Fallback title</h1>
<div class="site" th:text="${siteName}">Your site</div>
</main>
</body>
</html>
Render this template with a SpringTemplateEngine, then give the resulting HTML to the HTML-to-image renderer selected by your project. Thymeleaf itself does not rasterize HTML. Record that renderer’s browser/runtime and font requirements, and pin its version so a runtime upgrade cannot silently change card wrapping.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #3
- Sleek 7-in-1 USB-C Hub: Features an HDMI port, two USB-A 3.0 ports, and a USB-C data port, each providing 5Gbps transfer speeds. It also includes a USB-C PD input port for charging up to 100W and dual SD and TF card slots, all in a compact design.
- Flawless 4K@60Hz Video with HDMI: Delivers exceptional clarity and smoothness with its 4K@60Hz HDMI port, making it ideal for high-definition presentations and entertainment. (Note: Only the HDMI port supports video projection; the USB-C port is for data transfer only.)
- Double Up on Efficiency: The two USB-A 3.0 ports and a USB-C port support a fast 5Gbps data rate, significantly boosting your transfer speeds and improving productivity.
- Fast and Reliable 85W Charging: Offers high-capacity, speedy charging for laptops up to 85W, so you spend less time tethered to an outlet and more time being productive.
- What You Get: Anker USB-C Hub (7-in-1), welcome guide, 18-month warranty, and our friendly customer service.
Thymeleaf URL expressions can produce context-relative or absolute URLs. For social metadata, pass an already absolute HTTPS ogImageUrl to the article template and do not rely on a crawler resolving a relative path.
Generate on request or at publish time?
Synchronous endpoint
Rendering when /og/{slug}.png is requested is simple and keeps the image current. Bound input lengths, avoid network fetches during rendering, and add a public cache header. A crawler burst can otherwise cause repeated work.
Publish-time generation
For a content site, enqueue image generation when an article is published or its title changes. Store the PNG under an immutable key such as a content ID plus revision, then place that URL in the page metadata. This removes rasterization from crawler traffic and makes rollback explicit.
Cache identity
Choose either immutable URLs, such as /og/123-r7.png, or mutable slugs with a documented invalidation process. Keep the URL, bytes, width, height and media type synchronized. Do not let arbitrary query parameters alter a publicly cached image unless they are part of the cache key and validated.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsRank #4
- Dual Converters, Infinite Potential:Includes 2× USB C male to USB A female adapters and 2× USB A male to USB C female adapters. Perfect for a wide range of uses—tablets with Bluetooth keyboards, expand USB ports on macbook, and more. Two different converters for all your daily needs
- Next-Level 10Gbps & 3A Charging: No more slow 480Mbps, this usb to usb c adapter has a transfer speed of up to 10Gbps, allowing you to do more transferring in less time. This usb adapter fits both USB A and USB C charger, supporting up to 3A fast charging
- Upgraded Exquisite Craftsmanship: With an aluminum alloy housing and metal connector, the usbc to usb adapter is extremely durable and sturdy. Rigorously tested to withstand more than 10,000 times of plugging and unplugging, ensuring long-lasting performance
- Broad Compatible: The usb c to usb adapter widely supports all USB C/ USB A devices like laptops, tablets, cellphones, car chargers, and phone chargers. Such as compatible with MacBook Pro/Air 2023/2022, Thunderbolt 4/3 Devices,Apple MagSafe Watch 9/8/7/SE/Ultra, iPad Pro 2022/2021, Samsung Galaxy S23/S20/S10, and iPhone 17/16/15 Pro. Plug and play
- Please Note: To reach 10Gbps speed, keep the cable under 3.3 ft. For USB A Male to USB C adapters, try flipping the USB C connector. USB C Male to USB A adapters support bidirectional 10Gbps transfer within 3.3 ft
Fonts, internationalization and accessibility
- Load approved fonts and logos from classpath resources. Never map a request parameter directly to a filesystem path or remote URL.
- Test every supported language, including scripts that need fallback glyphs or shaping. A missing glyph can appear as a square even though Latin test titles look correct.
- Reserve space for the longest title and subtitle. Normalize whitespace and define a deterministic fallback when an article is missing.
- Keep the visual text readable at the final aspect ratio and write a descriptive
og:image:altvalue that explains the image rather than repeating a caption.
Security, reliability and cost controls
- Allow only known slugs or IDs and fetch their records through your normal authorization boundary. Do not expose arbitrary HTML, CSS, JavaScript or URL fetching through the image endpoint.
- Set maximum lengths for every user-controlled string and reject malformed path values before rendering.
- Use a bounded executor or a queue for expensive HTML rendering. Apply request timeouts and circuit breakers to any external renderer.
- Return
Content-Type: image/pngonly for actual PNG bytes. Log a correlation ID and the rendering failure while sending a normal 4xx or 5xx response. - Measure generation time, error count, cache hit rate and output size in your own monitoring. The available primary documentation does not establish a universal “best” dimension or performance percentage.
Validation checklist
- Request the final HTTPS image URL without cookies or authentication and verify a 200 response, the expected media type and the intended dimensions.
- Open the PNG with an image decoder and check that text, fallback glyphs, logo contrast and transparent areas (if used) are correct.
- Inspect the article HTML and confirm that
og:image, secure URL, type, width, height and alt all describe the same bytes. - Test a missing slug, a title at the maximum length, non-Latin text, a very long unbroken token and a renderer failure. Each case should have a deliberate result.
- Validate the public page with the social platforms used by your audience. Their preview caches may require a URL revision when an image changes.
Common failures and fixes
The preview is blank or shows an old card
Check that the image URL is absolute HTTPS, publicly reachable and not protected by a login, firewall or robots policy. If the bytes changed at the same URL, publish a versioned URL or use the platform’s documented refresh mechanism.
Text is clipped or overlaps
Measure with FontMetrics (Java2D) or constrain the CSS box (HTML). Reduce the font size, wrap earlier, cap lines and reserve space for the site label. Test the longest real titles rather than a short sample.
Accented characters appear as boxes
The runtime font lacks those glyphs. Bundle a font with the required coverage, register it explicitly, and test the deployment image rather than only a developer workstation.
Thymeleaf renders HTML but no PNG is produced
That is expected: Thymeleaf is a server-side template engine. Add and configure a separate HTML-to-image renderer, document its browser and font dependencies, and handle its timeout or startup failure as an image-generation error.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Best Value
- 5-in-1 Connectivity: Equipped with a 4K HDMI port, a 5 Gbps USB-C data port, two 5 Gbps USB-A ports, and a USB C 100W PD-IN port. Note: The USB C 100W PD-IN port supports only charging and does not support data transfer devices such as headphones or speakers.
- Powerful Pass-Through Charging: Supports up to 85W pass-through charging so you can power up your laptop while you use the hub. Note: Pass-through charging requires a charger (not included). Note: To achieve full power for iPad, we recommend using a 45W wall charger.
- Transfer Files in Seconds: Move files to and from your laptop at speeds of up to 5 Gbps via the USB-C and USB-A data ports. Note: The USB C 5Gbps Data port does not support video output.
- HD Display: Connect to the HDMI port to stream or mirror content to an external monitor in resolutions of up to 4K@30Hz. Note: The USB-C ports do not support video output.
- What You Get: Anker 332 USB-C Hub (5-in-1), welcome guide, our worry-free 18-month warranty, and friendly customer service.
Images are generated repeatedly under load
Add a public cache policy, memoize by slug and content revision, or move generation to publish time and serve immutable files. Ensure the cache key includes every input that changes the pixels.
A failed request returns an HTML error saved as .png
Inspect the response status and Content-Type. Configure exception handling so failures use an appropriate HTTP error response; never label an error document as an image.
Or skip the browser setup
If your card is available as a public HTML route, ScreenshotNeo can capture that route as a PNG, JPEG, WebP or PDF through one GET request. It is useful when you want to keep the card’s layout in HTML/CSS without operating a browser runtime yourself.
For example, expose a deterministic route such as https://example.com/og-render/post-123, then call the API (see the ScreenshotNeo documentation):
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/og-render/post-123 -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com/og-render/post-123"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/og-render/post-123' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', bytes);
ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed as clean shots, and the response identifies the page verdict and billing result in X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try the capture before adding it to your Spring workflow.
Frequently Asked Questions
Can the image endpoint accept a title directly from a query parameter?
It can, but a slug or internal content ID is safer. Resolve the title from your published record, enforce length limits, and avoid turning arbitrary request text into a publicly cacheable asset.
Do I need both og:image and og:image:secure_url?
Emit both when you have an HTTPS image URL. Keep their values identical and keep the declared type and dimensions synchronized with the returned bytes.
What should change when a post title is edited?
Treat the edit as a new image revision. Generate a new immutable URL or invalidate the mutable URL before updating the page metadata, then verify the new bytes independently.
Recommended Free Tools
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

