~ / guides / Java Web Scraping: The Complete Guide

Java Web Scraping: The Complete Guide

MR
Marcus Reed
Founder & lead tester · about the author
the short version
  • jsoup is the default Java scraping library: it fetches a page and parses HTML with CSS selectors in about 10 lines.
  • For JavaScript-rendered pages, use HtmlUnit (a headless browser in pure Java) or Selenium (drives a real Chrome or Firefox).
  • Current versions as of June 2026: jsoup 1.22.2, HtmlUnit 5.1.0, Selenium 4.44.0. HtmlUnit moved to the org.htmlunit groupId.
  • Parsing a page is about 10 lines of jsoup. Staying unblocked across thousands of requests is the harder job, and that is what a scraper API handles.

I have written scrapers in several languages, and Java is the one I reach for when the work has to live inside an existing JVM service: a Spring backend, a Kafka pipeline, an Android data job. This guide is the map for that path. Which library does what, a full jsoup walkthrough you can compile, how to handle JavaScript-heavy pages with HtmlUnit and Selenium, and how to keep a scraper alive once a site pushes back. The code uses the documented API for each library at its current 2026 version. I point the examples at books.toscrape.com, a sandbox built for practice.

Is Java good for web scraping?

Yes, Java is a strong choice for web scraping, particularly when your code already runs on the JVM. The library stack is mature: jsoup for parsing, HtmlUnit and Selenium for JavaScript, and the standard java.net.http.HttpClient for raw requests. Java’s static types catch selector and field-mapping bugs at compile time, and its threading model handles large concurrent crawls without extra machinery.

Three things make Java pull its weight here:

Python has more scraping tutorials and a deeper bench of one-off scripts, which I cover in the Python guide. Java wins when the scraper is part of a larger typed system rather than a standalone script.

What are the main Java web scraping libraries?

Three tools cover the whole field, and the right one depends on whether the page needs JavaScript. Static HTML needs only jsoup. JavaScript-rendered pages need a browser engine, and there you choose between HtmlUnit (pure Java, fast) and Selenium (a real browser, maximum fidelity).

LibraryWhat it doesBest forRuns JavaScript
jsoupFetches a page and parses HTML with CSS selectorsStatic sites, the default starting pointNo
HtmlUnitHeadless browser implemented in pure JavaJavaScript pages without a real browser installYes
SeleniumDrives a real Chrome or Firefox via WebDriverComplex JS, logins, clicking, infinite scrollYes
java.net.http.HttpClientThe JDK’s built-in HTTP client (no parsing)Hitting JSON APIs, full control over requestsNo

A few notes from using all four:

How do you set up jsoup with Maven or Gradle?

Add a single dependency and let the build tool fetch it. jsoup is published to Maven Central under the org.jsoup groupId, and the current version in June 2026 is 1.22.2.

For Maven, add this to the <dependencies> block in pom.xml:

<dependency>
  <groupId>org.jsoup</groupId>
  <artifactId>jsoup</artifactId>
  <version>1.22.2</version>
</dependency>

For Gradle, add one line to build.gradle:

implementation 'org.jsoup:jsoup:1.22.2'

That is the whole install. jsoup has no required transitive dependencies, so nothing else is pulled in. If you prefer no build tool at all, download the single jsoup-1.22.2.jar from the jsoup site and add it to your classpath, but Maven or Gradle is the path I would take on any real project.

How do you scrape a website with Java step by step?

The loop is two steps: fetch the page into a Document with Jsoup.connect(), then pull the fields you want with CSS selectors. jsoup does both, so a complete static scrape is one file.

This program fetches the first catalogue page and prints the title and price for every book on it:

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;

public class BookScraper {
    public static void main(String[] args) throws IOException {
        String url = "https://books.toscrape.com/catalogue/page-1.html";

        Document doc = Jsoup.connect(url)
                .userAgent("Mozilla/5.0 (compatible; BookScraper/1.0)")
                .timeout(10_000)
                .get();

        Elements cards = doc.select("article.product_pod");
        System.out.println(cards.size() + " books on this page");

        for (Element card : cards) {
            String title = card.selectFirst("h3 a").attr("title");
            String price = card.selectFirst(".price_color").text();
            System.out.printf("%-8s %s%n", price, title);
        }
    }
}

Each part of that maps to one job:

Compile and run it with a build tool, or directly with a recent JDK:

java -cp jsoup-1.22.2.jar BookScraper.java

This is the approach I would ship for a static page. The selectors are the only part that changes per site, so the pattern transfers directly to any target whose data is in the initial HTML.

Follow the “next” link until it disappears. Most paginated lists put a next-page anchor at the bottom of each page, so you scrape a page, look for that link, resolve it to an absolute URL, and repeat until there is no next link.

jsoup resolves relative links for you when you read the abs: attribute prefix, which saves manual URL joining:

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import java.io.IOException;

public class CatalogueCrawler {
    public static void main(String[] args) throws IOException, InterruptedException {
        String url = "https://books.toscrape.com/catalogue/page-1.html";
        int pages = 0;
        int total = 0;

        while (url != null) {
            Document doc = Jsoup.connect(url)
                    .userAgent("Mozilla/5.0 (compatible; CatalogueCrawler/1.0)")
                    .timeout(10_000)
                    .get();

            total += doc.select("article.product_pod").size();
            pages++;

            Element next = doc.selectFirst("li.next a");
            url = (next != null) ? next.attr("abs:href") : null;

            Thread.sleep(1000); // be polite: one request per second
        }

        System.out.println("pages crawled: " + pages);
        System.out.println("total books:   " + total);
    }
}

Two details make this robust on a real target:

Against the full books.toscrape.com catalogue this pattern walks all 50 listing pages and the 1,000 books across them. The same “find the next anchor, resolve it, repeat” loop handles any next-button pagination.

How do you scrape JavaScript-heavy sites in Java?

Use a browser engine, because jsoup cannot run JavaScript. When a page loads its data with JavaScript after the initial response, that data is absent from the HTML jsoup downloads, so your selectors find nothing. You have two Java options: HtmlUnit, a headless browser written in pure Java, or Selenium, which drives a real Chrome or Firefox.

Option A: HtmlUnit (pure Java, no browser binary)

HtmlUnit runs JavaScript itself, so there is no separate browser to install. Note the groupId: the project moved to org.htmlunit, and the current version is 5.1.0. The old net.sourceforge.htmlunit coordinates are the legacy line, so use the new one.

<dependency>
  <groupId>org.htmlunit</groupId>
  <artifactId>htmlunit</artifactId>
  <version>5.1.0</version>
</dependency>

You let the page run its scripts, grab the rendered HTML, then hand that HTML to jsoup so your parsing code stays identical:

import org.htmlunit.WebClient;
import org.htmlunit.html.HtmlPage;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

public class JsScraper {
    public static void main(String[] args) throws Exception {
        try (WebClient client = new WebClient()) {
            client.getOptions().setJavaScriptEnabled(true);
            client.getOptions().setCssEnabled(false);

            HtmlPage page = client.getPage("https://example.com");
            client.waitForBackgroundJavaScript(5000);
            String html = page.asXml();

            Document doc = Jsoup.parse(html);
            // parse with the same selectors as before:
            // doc.select(".product") ...
        }
    }
}

What matters here:

Option B: Selenium (a real browser)

Selenium drives an actual Chrome or Firefox through WebDriver, so it renders anything a user sees, including the heaviest modern sites. The current version is 4.44.0, and recent Selenium manages the driver binary for you (Selenium Manager, built in since 4.6), so there is no separate driver download.

<dependency>
  <groupId>org.seleniumhq.selenium</groupId>
  <artifactId>selenium-java</artifactId>
  <version>4.44.0</version>
</dependency>
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

public class SeleniumScraper {
    public static void main(String[] args) {
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--headless=new");
        WebDriver driver = new ChromeDriver(options);

        try {
            driver.get("https://example.com");
            String html = driver.getPageSource();

            Document doc = Jsoup.parse(html);
            // doc.select(".product") ...
        } finally {
            driver.quit();
        }
    }
}

The pattern is the same as HtmlUnit: render in a browser, take getPageSource(), parse with jsoup. Always call driver.quit() in a finally block so the browser process does not leak.

Here is how the two compare:

HtmlUnitSelenium
EnginePure Java, headlessReal Chrome or Firefox
InstallOne Maven dependencyDependency plus a browser on the host
SpeedFaster, lighterSlower, heavier
Fidelity on modern JSGood, can lagHighest, matches a real user
Best forCI jobs, moderate JSComplex apps, logins, clicking

The quick test for which path you need: fetch a page with jsoup and search the result for a value you can see in the browser. When it is missing, the page is JavaScript-rendered and you move to HtmlUnit or Selenium.

How do you avoid getting blocked when scraping in Java?

Look like a normal browser, slow down, and spread requests across IP addresses. A site flags scrapers by three signals: requests that arrive too fast, headers that look automated, and too much traffic from one IP. Each has a countermeasure that applies whether you are using jsoup, HtmlUnit, or Selenium.

Block signalWhat triggers itFix
Bad headersjsoup’s default User-Agent, no other headersSet a realistic User-Agent with .userAgent(...) and add browser-like headers
Request rateDozens of requests per second from one clientAdd Thread.sleep(), respect 429 responses, back off
Single IPThousands of requests from one addressRotate IPs or proxies, or use a scraper API
No JavaScriptHTTP-only fetches that never run scriptsRender with HtmlUnit or Selenium when the site checks

These measures carry a small project a long way. The trouble starts at scale: rotating a large IP pool, solving CAPTCHAs, and handling per-site quirks becomes its own engineering job, and it pulls focus from the data you actually want. That is where a scraper API like ChocoData fits the category, with a universal endpoint plus 453 dedicated endpoints that return the page so your Java code keeps working unchanged while the blocking is handled upstream. You point Jsoup.connect() at the API instead of the target, parse the response the same way, and skip the proxy and CAPTCHA plumbing. I cover that full tradeoff, and the legal side, in the web scraping guide and in how to scrape without getting blocked.

For the scraping itself, the stack in this guide will carry you a long way. Take the pagination crawler, point it at a site you care about, adjust the selectors until the fields come out clean, and add the anti-block measures as the target pushes back.

FAQ

Is Java good for web scraping?

Yes, especially if your stack is already on the JVM. jsoup handles static HTML, HtmlUnit and Selenium handle JavaScript, and the type system and threading model suit large, long-running crawls. Python has more tutorials, but Java's libraries are mature and fast.

Can jsoup scrape JavaScript-rendered pages?

No. jsoup fetches HTML over HTTP and does not run page scripts, so content injected by JavaScript is missing from what it parses. For those sites use HtmlUnit, which executes JavaScript in pure Java, or Selenium, which drives a real browser.

Do I need Maven or Gradle to use jsoup?

It is the easy path. Add one dependency to pom.xml or build.gradle and the build tool fetches jsoup for you. You can also download the single jsoup jar and put it on your classpath manually, but a build tool saves you from managing transitive dependencies by hand.

MR
Marcus Reed
I've built and run web scrapers for the better part of a decade. On this site I put scraper APIs and scraping tools through real jobs against real targets, then write up what actually holds up.