Use a real browser engine when the page must render JavaScript. In Java, Playwright is the best default for modern single-page applications, screenshots, PDFs, and browser-faithful behavior. HtmlUnit is a lighter, GUI-less Java browser model. GraalJS can execute JavaScript but cannot render a website, while JxBrowser embeds a commercial browser engine. Java’s HttpClient alone only downloads response bytes; it does not run scripts or build a DOM.
First decide whether you have a page URL or a script URL
These are different operations:
- Page URL: the website you want to render, such as
https://example.com. Navigate to it with a browser API. - Script URL: an external JavaScript resource, such as
https://cdn.example.com/widget.js. Load the page first, then insert a<script src="...">element into that document.
Fetching the page with HttpClient and searching the returned HTML works only when the desired data is already in the server response. An HTTP client has no browser DOM, CSS layout engine, event loop, cookie-aware page state, or JavaScript runtime connected to the document. A client-rendered React, Vue, Angular, or similar application therefore appears incomplete.
Render and inject a script with Playwright Java
Playwright Java drives a real browser engine and is the practical choice when compatibility with current websites matters. It executes page scripts, loads subresources, maintains cookies and browser state, and exposes DOM, screenshots, and PDF APIs.
Minimal runnable example
Add the current Playwright Java dependency and browser binaries according to the project’s installation instructions, then run this class:
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →import com.microsoft.playwright.*;
public class RenderPage {
public static void main(String[] args) {
try (Playwright pw = Playwright.create();
Browser browser = pw.chromium().launch(
new BrowserType.LaunchOptions().setHeadless(true))) {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://example.com");
page.addScriptTag(new Page.AddScriptTagOptions()
.setUrl("https://cdn.example.com/widget.js"));
// Replace this with a signal that proves your application is ready.
page.locator("#app-ready").waitFor();
String renderedHtml = page.content();
System.out.println(renderedHtml);
}
}
}
page.navigate loads the page. addScriptTag inserts a script element whose source is the supplied URL and completes when that script has loaded or been injected. page.content() returns the current document HTML, including the doctype. If the selector in the example does not exist on your site, substitute a real readiness condition or the wait will time out.
Injecting into an existing document
Use addScriptTag after navigation when the script expects a DOM, cookies, or application state. A script URL is not a replacement for navigation: loading the JavaScript file directly gives you source code, not the page it modifies. The target URL must actually serve JavaScript, and a site’s Content Security Policy, network failure, authentication requirement, or script error can prevent useful execution. Inspect the browser console and network events when injection fails.
Waiting for the rendered state
Navigation includes fetching and parsing the document, running scripts, loading resources, and firing DOMContentLoaded and load events. Modern applications can continue fetching data and changing the UI after those events, so “load” is not a universal definition of ready.
- Selector state: wait for the element that contains the data you need, for example
page.locator("article h1").waitFor(). - URL state: after a click that causes client-side routing, wait for the expected URL before reading the page.
- Network response: wait for the known API response that supplies the rendered data, then inspect the DOM.
- Application signal: have the application set a test-ready attribute or flag and wait for that signal.
A fixed sleep is usually slower and less reliable: it can expire before a slow API response arrives or waste time on a fast run. Use a bounded timeout and a condition tied to the application state instead.
Capture a screenshot or PDF after rendering
Perform the same readiness wait before output. Playwright can then capture the viewport or full page and produce a PDF from the rendered browser state. For full-page screenshots, allow lazy images to load or scroll the page as required by the application; otherwise an image that loads only when visible may be absent.
Rank #2
Use HtmlUnit for a Java-native, GUI-less browser model
HtmlUnit describes itself as a “GUI-Less browser for Java programs.” Its WebClient handles HTTP requests, redirects, cookies, browser state, JavaScript execution, and DOM interaction without launching a graphical browser.
import org.htmlunit.WebClient;
import org.htmlunit.html.HtmlPage;
public class HtmlUnitRender {
public static void main(String[] args) throws Exception {
try (WebClient client = new WebClient()) {
HtmlPage page = client.getPage("https://example.com");
String visibleText = page.asNormalizedText();
System.out.println(visibleText);
}
}
}
asNormalizedText() is intended to represent visible text, with whitespace normalized and hidden script and style content ignored. You can also inspect the DOM, submit forms, and execute page JavaScript. Use the current Maven coordinates and version shown on HtmlUnit’s official getting-started page rather than copying an old version into a new project.
HtmlUnit limitations
HtmlUnit emulates browser behavior; it is not equivalent to a current Chromium instance. Pages that depend on cutting-edge browser APIs, precise CSS layout, WebGL, complex media, or browser-specific behavior can differ. HtmlUnit stops JavaScript at the first unhandled exception by default, unlike a normal browser. If you need the rest of the page to continue after a page error, configure client.getOptions().setThrowExceptionOnScriptError(false), while still logging and reviewing those errors. Disabling exceptions can hide a genuine application failure, so it should be a deliberate compatibility choice.
Where GraalJS and JxBrowser fit
GraalJS: JavaScript execution without website rendering
GraalVM’s org.graalvm.polyglot.Context is the preferred embedding interface for running JavaScript in Java. It is useful for calculations, transformations, or evaluating source fetched by your application. GraalJS does not provide the browser DOM, CSS layout, browser security model, navigation, or the page-resource lifecycle. It therefore cannot, by itself, render a modern website or reproduce what a user sees. The older JSR-223 ScriptEngine path remains a compatibility option, but current GraalVM releases require explicit script-engine dependencies and module setup.
JxBrowser: an embedded commercial browser
JxBrowser is appropriate when a desktop or Java application must embed a full browser engine in-process. Its Frame.executeJavaScript(String) method runs code in a loaded page and converts JavaScript values, including DOM wrappers, between JavaScript and Java. It brings the deployment and licensing considerations of a commercial embedded SDK; verify current terms with TeamDev before selecting it.
Choose the renderer for your requirement
| Requirement | Playwright Java | HtmlUnit | GraalJS | JxBrowser |
|---|---|---|---|---|
| Modern browser fidelity | Strong; drives a real browser engine | Moderate; compatibility varies | None by itself | Strong; embedded browser SDK |
| JavaScript execution | Yes | Yes | Yes | Yes |
| DOM, CSS, and layout | Yes | Browser-like model with limits | No | Yes |
| Headless/server use | Strong | Strong | Strong | Depends on deployment |
| External script URL | addScriptTag(...setUrl(...)) |
Possible through DOM/script APIs; details vary | Fetch and evaluate source yourself | Execute code in the loaded frame |
| Best fit | Testing, scraping, screenshots, PDFs, modern SPAs | Lightweight Java-native extraction | Non-browser JavaScript computation | Product UI or embedded browser features |
For a server-side renderer, start with Playwright unless you have confirmed that HtmlUnit’s compatibility is sufficient. Choose HtmlUnit when avoiding a full browser process and keeping the API entirely Java-native is more important than pixel-level fidelity. Choose GraalJS only when you do not need a website. Choose JxBrowser when an embedded browser is itself part of your product.
Reliability, performance, and operational details
- Reuse browser processes: create one Playwright and browser instance for a worker, then use isolated browser contexts per job. Close pages and contexts when work finishes.
- Bound every wait: set navigation, selector, and network timeouts so a stalled origin cannot occupy a worker indefinitely.
- Prefer deterministic readiness: a selector, response, URL, or application flag is easier to diagnose than an arbitrary delay.
- Control state deliberately: supply cookies, authentication headers, locale, timezone, and viewport when the page changes by user or region.
- Limit unnecessary work: block known ads, trackers, or irrelevant resource types only when doing so cannot change the application state you need.
- Protect credentials: keep login cookies, authorization headers, and injected script URLs out of logs and screenshots.
- Record diagnostics: capture the final URL, console errors, failed requests, HTTP status, and a small HTML or screenshot sample when a job fails.
Common failures and fixes
“The HTML contains no data”
Cause: an HTTP client fetched the shell before JavaScript populated it. Fix: navigate with Playwright or HtmlUnit and wait for the data-bearing element or API response.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware match“The selector timeout expired”
Cause: the selector is wrong, the route is not authenticated, the application failed, or the page needs a different readiness signal. Fix: inspect the final URL and console, confirm the element in browser developer tools, and wait for the actual route or response that creates it.
“The script URL loaded but nothing changed”
Cause: the file is not the correct entry point, it expects configuration, it ran before the required element existed, or a policy/network error blocked it. Fix: navigate first, inject after the target element exists, provide the expected configuration, and inspect console and request failures.
“HtmlUnit stops after a page error”
Cause: its default behavior stops JavaScript at the first unhandled exception. Fix: set setThrowExceptionOnScriptError(false) when continuing is acceptable, and keep the logged error for diagnosis. Move to Playwright if the site depends on browser APIs HtmlUnit does not emulate.
Rank #4
“The browser will not launch in CI”
Cause: Playwright’s browser binaries or required system libraries are absent, or the sandbox policy of the runner prevents launch. Fix: install the browsers and operating-system dependencies in the build image, use headless mode, and check the runner’s sandbox configuration.
Free tools Windows power users keep installed
One-click scans. No signup required.
“A CAPTCHA or bot-check page is rendered”
Cause: the origin is challenging automation, not a Java rendering bug. Fix: respect the site’s access rules, use an authorized authenticated flow, and treat the challenge page as a failed business result rather than scraping through it.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server when you need a rendered image or PDF rather than a browser embedded in your Java service. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.
One GET request returns PNG, JPEG, WebP, or PDF. The API also supports full-page capture with lazy images, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDF paper and margin settings, custom CSS and JavaScript, clicks, selector or network-idle waits, request and resource blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage data, and an OpenAPI specification. Parameter names used by other screenshot APIs are accepted to ease migration.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
See the ScreenshotNeo API documentation for option names and response handling. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients, so an AI agent can perform the capture without your service managing browser binaries.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; Growth is $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to try it without a card.
Best Value
Frequently Asked Questions
Can I inject a JavaScript file before calling page.navigate?
No. A script tag belongs to a document. Navigate to the page first, then add the script, or register an initialization hook that runs before page scripts when your use case requires that timing.
How do I know whether to wait for load, a selector, or an API response?
Wait for the condition that proves the exact data you will consume is ready. Use a selector for visible content, a response for a known data request, a URL for client-side routing, or an application-controlled flag for a complex workflow.
Does HtmlUnit produce pixel-identical screenshots?
No. It provides a Java-native browser model, but its emulation can differ from current Chromium in browser APIs, CSS, and layout. Use Playwright when visual fidelity matters.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Is GraalJS a replacement for Playwright?
No. GraalJS runs JavaScript source but does not supply the DOM, CSS layout, navigation, or browser resource lifecycle required to render a website.
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.

