Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Handle 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Tool 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

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.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.