The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use Playwright for Java when you need a real browser to load a page, execute its JavaScript, wait for client-side content, and capture the rendered DOM, screenshot, or PDF. Navigate to the page URL first, then inject an external script URL with page.addScriptTag() only when the page needs an additional script. Java HttpClient fetches bytes but does not provide a DOM, layout engine, browser event loop, or JavaScript runtime.
The short answer: choose a browser when you need rendering
A JavaScript-heavy site is not rendered by downloading its HTML. The initial response may contain only an app shell; JavaScript then requests data, builds the DOM, applies CSS, and updates the screen. For that workflow, drive a browser engine.
- Playwright Java: best default for modern frameworks, client-side routing, screenshots, PDFs, testing, and scraping.
- HtmlUnit: a GUI-less, Java-native browser model when compatibility with the target site is sufficient.
- GraalJS: executes JavaScript code but is not a website renderer; it has no browser DOM or CSS layout engine by itself.
- JxBrowser: an embedded commercial browser SDK for desktop or Java applications that need an in-process browser.
The critical distinction is between two URLs. A page URL is the site you navigate to. A script URL is an external .js resource that you add to an already loaded document. Fetching either URL with HttpClient alone never turns the response into a rendered page.
Recommended Free Tools
Render a page and inject a script with Playwright Java
Playwright launches a real Chromium, Firefox, or WebKit browser. Navigation fetches and parses the document, executes scripts, loads resources, and fires lifecycle events. Dynamic applications can continue changing after those events, so wait for a condition that proves the state you need.
Complete example
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 the element or signal your application exposes.
page.locator("#app-ready").waitFor();
String renderedHtml = page.content();
System.out.println(renderedHtml);
}
}
}
page.addScriptTag() inserts a <script> element whose source is the supplied URL and completes when the script has loaded or has been injected. page.content() returns the current document, including the doctype. In a production project, add the Playwright Java dependency and install the browser binary using the current Playwright Java installation instructions; browser versions and dependency coordinates change over time.
Wait for the application, not an arbitrary delay
There is no universal moment at which every website is “loaded.” A fixed sleep is either wasteful or flaky. Pick the narrowest observable condition that matches your job:
- Selector: wait for a meaningful element, such as a results table or
#app-ready. - Text: wait for a heading or status message that appears only after data is rendered.
- URL: after a click or client-side route change, wait for the expected URL.
- Network response: wait for the known API response that supplies the content, then inspect the DOM.
- Application signal: have the application expose a readiness attribute or test flag and wait for it.
Use a navigation timeout appropriate to your environment, and keep the selector or response specific. Waiting for “network idle” can help for pages that finish a bounded burst of requests, but long polling, analytics, and sockets can prevent that state from ever being reached.
When the script needs page state
Inject after navigation if the script expects a DOM or browser globals. If it must run before the application’s own scripts, arrange the injection during context/page initialization instead of adding it after the page has already booted. Preserve cookies, headers, and storage in the same browser context so the injected code sees the same session as the page.
Extract text, screenshots, or PDFs
Once the readiness condition is satisfied, use page.locator(...) for structured extraction, page.screenshot() for an image, or page.pdf() in a Chromium context for a PDF. Capture only after lazy content, route transitions, and any required interactions have completed.
Rank #2
HtmlUnit for a Java-native, GUI-less browser
HtmlUnit provides a browser-like programming model entirely within Java. Its WebClient handles HTTP requests, cookies, redirects, browser state, and JavaScript; getPage() returns an HtmlPage whose DOM you can inspect and manipulate.
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);
}
}
}
The current getting-started documentation uses Maven coordinates under org.htmlunit:htmlunit; check the official HtmlUnit page for the current version before pinning one. asNormalizedText() is intended to represent visible text, normalizing whitespace and ignoring hidden script and style content.
Outdated 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 matchWindows 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 reinstallHandle page-script failures deliberately
HtmlUnit stops JavaScript at the first unhandled exception by default, unlike a normal browser that often logs the error and continues. If the target page contains an unrelated script error, configure client.getOptions().setThrowExceptionOnScriptError(false), capture the logs, and verify that the content you need was actually produced. HtmlUnit emulates browser behavior and may differ from current Chromium when a site depends on newer browser APIs.
Why GraalJS alone does not render a website
GraalJS is appropriate when your Java program must evaluate JavaScript source, transform data, or run application logic outside a browser. GraalVM recommends org.graalvm.polyglot.Context as the embedding interface. The older JSR-223 ScriptEngine route remains a compatibility option but requires explicit engine dependencies and module setup in current GraalVM releases.
Evaluating downloaded source with GraalJS does not supply window, a live DOM, CSS layout, browser security behavior, navigation, resource loading, or the page’s event lifecycle. If the script expects those objects, use Playwright, HtmlUnit, or an embedded browser instead.
When JxBrowser is the right choice
JxBrowser embeds a browser engine in a Java application. Its Frame.executeJavaScript(String) API runs code in a loaded frame and converts JavaScript values, including DOM wrappers, between Java and JavaScript. It fits desktop products and other applications that need a browser UI or browser engine in-process. It is a commercial SDK; verify current licensing and deployment terms with TeamDev before committing to it.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsTool comparison
| Requirement | Playwright Java | HtmlUnit | GraalJS | JxBrowser |
|---|---|---|---|---|
| Modern browser fidelity | Strong; drives real browser engines | Moderate; compatibility limits apply | None by itself | Strong; embedded browser SDK |
| JavaScript execution | Yes | Yes | Yes | Yes |
| DOM, CSS, and layout | Yes | Browser-like model | 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 a loaded frame |
| Best fit | Testing, scraping, screenshots, PDFs, modern SPAs | Lightweight Java extraction and automation | Non-browser JavaScript computation | Product UI or embedded browser features |
Or skip the browser setup: ScreenshotNeo
If your goal is a screenshot or PDF rather than browser automation inside your Java process, ScreenshotNeo provides a single HTTP endpoint. It accepts a URL and returns PNG, JPEG, WebP, or PDF. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled.
Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the result with X-Page-Verdict and X-Billed. The service also has an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
One-call cURL example
See the ScreenshotNeo API documentation for parameter details.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Java, Python, and Node.js clients
Java can call the same endpoint with its standard HTTP client:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
public class ScreenshotNeoShot {
public static void main(String[] args) throws Exception {
String endpoint = "https://api.screenshotneo.com/v1/shot?access_key=YOUR_API_KEY&url=https%3A%2F%2Fstripe.com";
HttpRequest request = HttpRequest.newBuilder(URI.create(endpoint)).GET().build();
HttpResponse<byte[]> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofByteArray());
Files.write(Path.of("shot.webp"), response.body());
System.out.println(response.headers().map());
}
}
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}`);
Beyond full-page capture with lazy images, ScreenshotNeo supports CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper size/margins/orientation/page ranges, HTML/CSS to image, custom JavaScript and CSS, pre-capture clicks, hidden selectors, waits for selectors or network idle, ad/tracker/request/resource blocking, custom headers/cookies/user agents and Authorization, timezone and geolocation, transparent backgrounds, image resizing, user-selected cache TTLs, signed public image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, an OpenAPI specification, and parameter names compatible with other screenshot APIs.
Rank #4
Plans
| Plan | Included shots | Price |
|---|---|---|
| Free | 1,000 per month | $0; no card |
| Starter | 3,000 | $5 |
| Growth | 15,000 | $15 |
| Pro | 60,000 | $39 |
| Scale | 250,000 | $99 |
| Business | 1,000,000 | $249 |
Yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Troubleshooting JavaScript rendering
HttpClient returns the app shell
Cause: an HTTP client received the initial bytes but never executed the browser code. Fix: use Playwright or HtmlUnit, then wait for a page-specific readiness condition before reading content.
addScriptTag fails or the script has no effect
Cause: the URL is inaccessible, blocked by the page’s policy, expects a particular execution order, or runs before required globals exist. Fix: verify the URL in the same browser context, inject after navigation when it needs the DOM, and inspect console and network errors. If the application requires the script before its own startup code, configure an early initialization hook rather than adding it after load.
The selector timeout expires
Cause: the selector is wrong, the route failed, the content is inside a frame, or the application never reached the expected state. Fix: log the current URL, inspect the DOM and console, wait for the API response that drives the view, and target the correct frame or shadow-DOM boundary.
HtmlUnit stops unexpectedly
Cause: an unhandled page-script exception. Fix: temporarily disable throwing on script errors, retain the error log, and confirm that the required DOM was produced. If the site uses browser APIs HtmlUnit does not emulate, move to Playwright.
Best Value
Rendered output differs from a user’s browser
Cause: viewport, device scale, timezone, geolocation, cookies, authentication, or browser engine differences. Fix: create the context with the same relevant settings and persist the required session state. For highly browser-specific pages, prefer Playwright’s current engine over a compatibility emulator.
Reliability, performance, and security practices
- Reuse a Playwright browser process and create isolated contexts per job; launching a fresh browser for every URL adds overhead.
- Set explicit navigation and action timeouts, but make readiness conditions semantic rather than replacing them with longer sleeps.
- Close pages, contexts, and browsers with try-with-resources or a finally block so failed jobs do not leak processes.
- Cache immutable results where appropriate, but invalidate when the page’s data or authentication state changes.
- Keep API keys, cookies, Authorization headers, and injected scripts out of logs. Treat third-party script URLs as executable code and allow-list them.
- Use a restricted browser context for untrusted pages, avoid exposing host files or secrets, and do not grant more network access than the job requires.
For screenshot workloads, a hosted API can remove browser installation and maintenance from your service. ScreenshotNeo’s verdict and billing headers let a job distinguish a successful clean capture from a blocked or failed page without charging for those failed cases.
FAQ
Can I inject a script URL before navigating?
A script tag belongs to a document. Navigate to the target page first unless you deliberately configure an early page-initialization hook for code that must run before the application starts.
Which option is best for a Java server that never displays a window?
Start with headless Playwright for modern sites and screenshots. Choose HtmlUnit when its compatibility is adequate and a lightweight Java-native model is more important than full browser fidelity.
Is GraalJS a replacement for Playwright?
No. GraalJS runs JavaScript; it does not render a website. It is useful for JavaScript computation that does not depend on browser DOM, CSS, or page resources.
Frequently Asked Questions
Can I inject a script URL before navigating?
A script tag belongs to a document. Navigate to the target page first unless you deliberately configure an early page-initialization hook for code that must run before the application starts.
Which option is best for a Java server that never displays a window?
Start with headless Playwright for modern sites and screenshots. Choose HtmlUnit when its compatibility is adequate and a lightweight Java-native model is more important than full browser fidelity.
Is GraalJS a replacement for Playwright?
No. GraalJS runs JavaScript; it does not render a website. It is useful for JavaScript computation that does not depend on browser DOM, CSS, or page resources.
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.

