What Is Jsoup? β Definition and How It Works
Learn what jsoup is, how its lenient HTML parser and CSS selectors work in Java, and where its limits are.
jsoup is a Java library for parsing HTML, traversing it with CSS selectors, extracting data, and sanitising untrusted markup. It parses the way a browser does β leniently β rather than requiring well-formed XML.
1. The Core API
Document doc = Jsoup.connect("https://example.com/products")
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
.timeout(30_000)
.get();
for (Element card : doc.select("div.product-card")) {
String title = card.select("h2.title").text();
String price = card.select("span.price").text();
String href = card.select("a[href]").attr("abs:href");
System.out.printf("%s | %s | %s%n", title, price, href);
}
The abs: attribute prefix resolves a relative URL against the document's base β the single most useful convenience in the library, and the thing hand-written extraction most often gets wrong.
2. Lenient Parsing Is the Point
Real HTML is broken: unclosed tags, misnested elements, stray attributes, invalid nesting. An XML parser rejects it; jsoup applies the HTML5 parsing algorithm and builds the same tree a browser would.
That means the DOM you select against matches what the browser shows, including implied elements such as an inserted tbody β which explains why a selector copied from browser dev tools works.
3. Selector Support
jsoup implements a substantial CSS selector subset plus useful extensions:
| Selector | Matches |
|---|---|
div.card > h2 |
Direct children |
a[href^=/product/] |
Attribute prefix |
li:nth-child(2n) |
Positional |
p:contains(Sale) |
Text content |
div:matches(^Price) |
Regex on text |
:not(.hidden) |
Negation |
td:matchesOwn(\\d+) |
Regex on own text only |
:contains and :matches have no CSS equivalent and are frequently the cleanest way to locate an element whose only distinguishing feature is its text.
4. Fetching Versus Parsing
Jsoup.connect() is a convenience HTTP client, not a full one. It handles cookies, redirects, headers, and form posts, and that is enough for simple cases.
For anything serious β connection pooling, HTTP/2, retry policies, proxy rotation β fetch with a dedicated client and hand the string to Jsoup.parse():
String html = httpClient.get(url);
Document doc = Jsoup.parse(html, url); // baseUri enables abs: resolution
Passing the base URI is what keeps abs:href working when you parse a string rather than fetch.
5. Sanitising Untrusted HTML
jsoup's other well-known role is XSS prevention through allowlist-based cleaning:
String safe = Jsoup.clean(userHtml, Safelist.basicWithImages());
This is genuinely different from escaping: it produces HTML that still renders, with only allowed elements and attributes surviving. For any application storing user-submitted markup, it is the standard Java answer.
6. What It Does Not Do
JavaScript. jsoup parses the HTML it is given. A client-rendered page yields an empty shell, and no jsoup configuration changes that β the fix is a rendering step upstream.
Browser behaviour. No cookies-across-navigation state machine, no fingerprint, no challenge handling.
XPath. Selectors only, which covers most needs but not all β XPath's ability to select a parent from a child has no direct CSS equivalent.
7. Practical Notes
- Use
text()for visible text andhtml()for markup.text()normalises whitespace, which is usually what you want. - Prefer
selectFirst()overselect().first()β it returns null rather than throwing on an empty result, so the null check is explicit. - Check for null on
selectFirst(). A missing element is the normal case when a site changes, and an unchecked null becomes aNullPointerExceptionin production. - Set a realistic timeout. The default is generous enough to hang a batch.
- Handle
maxBodySizeβ jsoup truncates large documents by default, which silently loses content on big pages.
That truncation default is the surprising one: a very large page parses successfully with its tail missing, which looks like a site change rather than a configuration limit.
8. Summary
jsoup parses real-world HTML the way a browser does, offers CSS selectors with useful text-matching extensions, resolves absolute URLs via abs:, and sanitises untrusted markup against an allowlist.
It does not execute JavaScript, so client-rendered pages need a rendering step first β and its default body-size limit will silently truncate very large documents unless raised.