Why ITextRenderer Ignores Internal Styles When Generating PDFs
ITextRenderer supports embedded CSS. Learn how XHTML validity, print media, base URLs, resource loading, and CSS support affect PDF styling.

Short answer: ITextRenderer does support CSS inside an embedded <style> element. When those styles appear to be ignored, the usual checks are: the generated document is valid XHTML, the rules apply to print media, linked resources resolve from the correct base URL, selectors match the generated markup, and the selected Flying Saucer artifact supports the CSS features you use.
ITextRenderer is part of Flying Saucer, an XML and CSS renderer rather than a browser. It expects well-formed XHTML and CSS. A browser may repair malformed HTML and still display it; Flying Saucer generally will not. The Flying Saucer documentation and FAQ describe this input model and note that PDF output is treated as print media.
What “ignored internal styles” usually means
The symptom can come from several different layers:
| Layer | What can be wrong | How to distinguish it |
|---|---|---|
| Markup | The generated XHTML is not well formed, or the style element is malformed or misplaced. | Save the exact string passed to the renderer and validate it as XHTML. |
| Media | Rules are scoped to screen, while PDF rendering uses print media. |
Temporarily use media="print" or media="all". |
| Resources | A linked stylesheet, font, image, or background URL cannot be fetched. | Check the base URL and the configured user-agent callback. |
| Cascade | A later rule, a more specific selector, or an inline declaration wins. | Try one obvious rule on a known element and remove competing rules. |
| Feature support | The CSS is valid but outside the selected renderer’s supported feature set. | Compare the required HTML/CSS features with the chosen Flying Saucer artifact. |
1. Start with valid XHTML
Validate the final document, not the template before interpolation. Close every element, escape text correctly, quote attributes, and use a complete XHTML structure. Do not assume that browser-tolerated HTML will be accepted by an XML parser.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style type="text/css" media="print">
body { font-family: sans-serif; color: #222; }
h1 { color: #0b4f71; }
.avoid-break { page-break-inside: avoid; }
</style>
</head>
<body>
<h1>Invoice</h1>
<p class="avoid-break">Content rendered by Flying Saucer.</p>
</body>
</html>
Inspect the generated output for unclosed tags, duplicate attributes, invalid nesting, unescaped ampersands, and characters emitted with the wrong encoding. Logging the final XHTML alongside the PDF request makes these failures much easier to isolate.
2. Account for print media
The Flying Saucer FAQ states that PDF output is treated as print media. A stylesheet that is explicitly limited to screen will not provide those declarations to the PDF renderer.
<link rel="stylesheet" type="text/css" href="css/report.css" media="print" />
<style type="text/css" media="all">
.screen-only { display: none; }
.print-only { display: block; }
</style>
Search the generated XHTML and CSS for media="screen", @media screen, and print rules that override your intended declarations. For a quick diagnostic, move one simple rule into an embedded style block with media="print". If that rule appears, the issue is probably media selection or linked-resource loading rather than a blanket inability to read internal CSS.
3. Verify the embedded style element
Embedded CSS is supported, but the renderer still needs valid XML around it and selectors that match the actual output. Check all of the following:

- The element is
<style type="text/css">(or has an equivalent valid declaration). - The style element is inside the document head or another location accepted by your XHTML parser.
- CSS comments and braces are closed.
- Template variables did not insert an unescaped
<,&, or quote into the style block. - The selector matches generated elements and class names exactly.
- A later or more specific rule is not overriding the declaration.
Use a minimal probe before debugging a large stylesheet:
<style type="text/css" media="print">
body { background-color: #eeeeee; }
#css-probe { color: #cc0000; font-size: 24pt; }
</style>
...
<p id="css-probe">CSS probe</p>
This does not prove that every modern CSS feature is supported; it separates parsing and cascade problems from feature gaps.
4. Set the document URL and base URL
When you pass a string to ITextRenderer, resource resolution depends on the document URL or base URL supplied to the renderer. Relative stylesheet, image, font, and background URLs need a meaningful base.
String xhtml = Files.readString(Path.of("report.xhtml"), StandardCharsets.UTF_8);
ITextRenderer renderer = new ITextRenderer();
// The second argument establishes the base used for relative resources.
renderer.setDocumentFromString(xhtml, Path.of("report.xhtml").toUri().toString());
renderer.layout();
try (OutputStream out = Files.newOutputStream(Path.of("report.pdf"))) {
renderer.createPDF(out);
}
The exact overload and setup depend on the Flying Saucer version. The diagnostic point is the same: inspect the document-setting call and confirm what URL the renderer uses as the base. If the XHTML is assembled in memory, pass a base URL that can resolve every relative reference.
5. Check linked-resource loading
Flying Saucer uses its user-agent callback to retrieve XML, CSS, images, and other resources and to resolve URIs. A custom callback can therefore cause an apparently unrelated CSS failure.
- Log every stylesheet URI the callback receives.
- Attempt to fetch each URI from the same runtime and identity as the renderer.
- Confirm that relative paths resolve under the configured base URL.
- Check file permissions, classpath handling, TLS certificates, authentication, and redirects.
- Verify that the callback returns the correct content type and bytes.
A classpath-prefixed URI such as classpath:templates/css/report.css may require explicit application support. A Flying Saucer users-group report described a case where classpath-prefixed resources failed while absolute file:// paths worked; treat that as an anecdotal clue to inspect your resolver, not as proof that every classpath URL fails.
6. Use a complete Java example
import com.lowagie.text.DocumentException;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public final class PdfWithCss {
public static void main(String[] args) throws IOException, DocumentException {
Path source = Path.of("report.xhtml");
Path target = Path.of("report.pdf");
String xhtml = Files.readString(source, StandardCharsets.UTF_8);
ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(xhtml, source.toUri().toString());
renderer.layout();
try (OutputStream output = Files.newOutputStream(target)) {
renderer.createPDF(output);
}
}
}
Use the dependency and Java version required by the artifact version in your project. The current Flying Saucer project documentation describes the regular PDF artifact as using OpenPDF and lists Java 11+ from version 9.5.0, Java 17+ from 9.6.0, and Java 21+ from 10.0.0.
7. Choose the renderer path that matches your CSS
| Artifact path | Consider it when | Check before changing |
|---|---|---|
flying-saucer-pdf |
Your existing application needs Flying Saucer’s regular PDF output. | OpenPDF integration, Java runtime compatibility, and the CSS features your document requires. |
flying-saucer-chrome-pdf |
Your content depends on modern HTML5 or CSS3 behavior. | Chrome headless deployment, runtime dependencies, startup cost, and operational compatibility. |
Switching artifacts is not a fix for malformed XHTML or a missing base URL. First establish that the document and resources are correct, then decide whether the selected renderer supports the remaining CSS.
8. Troubleshooting checklist
| Symptom | Likely cause | Fix |
|---|---|---|
| No styles at all | Malformed XHTML or style markup. | Validate the final XHTML and reduce it to a minimal embedded rule. |
| Embedded rules work, linked rules do not | Base URL or resource callback problem. | Set an explicit document URL and log resource retrieval. |
| Only some rules work | Selector mismatch, cascade, or unsupported property. | Inspect generated class names, specificity, and feature support. |
| Screen layout differs from PDF | Print media selection. | Use print or all and add print-specific rules. |
| Images or fonts disappear with CSS | Relative URI, permissions, authentication, or callback failure. | Resolve the absolute URI from the renderer’s base and fetch it directly. |
| Modern layout does not render | Renderer feature gap. | Compare the required CSS with the regular PDF path or evaluate the Chrome-backed artifact. |
| PDF generation fails after a Java upgrade | Artifact/runtime mismatch. | Match the Flying Saucer version to its documented Java requirement. |
9. Performance, reliability, and cost considerations
- Performance: Resource loading, font discovery, image decoding, and layout complexity usually dominate work. Reuse stable CSS and avoid fetching unnecessary assets.
- Reliability: Make resource URLs deterministic, log the final XHTML and base URL, and fail clearly when required assets cannot be loaded.
- Repeatability: Pin the renderer artifact and Java runtime. A CSS feature that works in a browser is not automatically portable to every Flying Saucer version.
- Operational cost: Browser-backed rendering can require more deployment resources than the regular PDF path. Compare the features you need with the runtime and deployment requirements before switching.
Or skip the browser setup
If your goal is a clean PDF or screenshot of a URL rather than maintaining a renderer, ScreenshotNeo provides one GET request to its capture API. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the verdict and billing status in headers. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf.
See the ScreenshotNeo API documentation for PDF options and the full set of capture parameters.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' }); const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
There are 1,000 screenshots per month on the free plan with no card. Paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Does ITextRenderer support a style tag?
Yes. Embedded CSS is supported when the XHTML and CSS are valid and the rules apply to the PDF’s print media.
Should I use media="all" or media="print"?
Either can make rules available to PDF output. Use print for print-only declarations and all for rules shared across media.
Can a valid stylesheet still fail?
Yes. A valid stylesheet can be unreachable, based on the wrong URL, overridden by the cascade, or dependent on CSS features unsupported by the selected renderer.
What information is needed to diagnose one failing PDF?
Provide the final XHTML, complete CSS, Flying Saucer artifact and version, Java version, document-setting call and base URL, custom resource-loader configuration, and parser/resource logs.


