Memra

Equality, the URI class, and form encoding

◈ 4 cards

Why URL.equals blocks on DNS and must never key a hash structure, what the pure-syntax URI class gives you instead, and how to build an x-www-form-urlencoded query one component at a time.

equals() on a URL goes to the network

Two URL objects are equal when they point at the same resource on the same host, port and path, with the same query and fragment. The surprise is in same host: to decide it, URL.equals() resolves both hostnames through DNS, so http://ibiblio.org/ and http://www.ibiblio.org/ compare equal when they resolve to the same address.

That makes equals() — and therefore hashCode(), which must agree with it — a blocking network operation. A HashSet<URL> built from the links on one page can stall for as long as the resolver takes, behave differently on a different network, and hang entirely behind a broken DNS server. Never put a URL in a HashMap, HashSet, or anything else that hashes. Store URI and convert on the way out.

sameFile() is the same comparison, DNS and all, minus the fragment: …/page.html#a and …/page.html#b are sameFile but not equals. And URL does not implement Comparable, so you cannot sort a list of them without a comparator.

URI: pure syntax

java.net.URI is built from strings and throws the checked URISyntaxException when they break the grammar. It needs no protocol handler, so urn:, tel: and schemes invented tomorrow all parse. In exchange it does no I/O at all.

Its comparison behaviour is everything URL's is not. URI.equals() is a string comparison — scheme and authority case-insensitive, the rest case-sensitive — with no lookup, and escapes are not decoded first, so …/A and …/%41 are unequal. URI implements Comparable, so URIs sort. Three methods do the syntax work: resolve(String) merges a relative reference against this URI, relativize(URI) reverses that, and normalize() removes . and .. segments. Cross over with toURI() and toURL() at the boundary.

x-www-form-urlencoded

A query component may carry only a restricted ASCII set, so form data is encoded before it goes into the URL. URLEncoder.encode leaves letters, digits and the four characters . - * _ alone, turns a space into +, and converts everything else — including every non-ASCII character — into percent escapes of its UTF-8 bytes. UTF-8 is the only charset you should ever pass.

The modern signature takes a Charset and throws nothing:

URLEncoder.encode(s, StandardCharsets.UTF_8);

The older encode(String, String) throws UnsupportedEncodingException for a charset name the VM does not know, and the one-argument encode(String) is deprecated because it silently uses the platform default — the classic bug that works on your laptop and mangles data on the marker's machine.

The critical property: the encoder is blind. It cannot tell a slash that is structure from a slash that is data, so it encodes both. Encode one component at a time and assemble the URL yourself.

Worked example — building a query string

You want to search the course site for the phrase I/O & streams, filtered to topic=urls. Encoding each value separately gives:

topic  ->  urls
q      ->  I%2FO+%26+streams

http://data.example.org:8080/comp348/search?topic=urls&q=I%2FO+%26+streams

The slash became %2F and the ampersand %26 inside the value, while the ?, the = and the & that separate the pairs stayed literal — because you wrote those, not the encoder. Hand the whole URL to URLEncoder.encode instead and you get http%3A%2F%2Fdata.example.org…: a string that is no longer a URL at all, only a very long, very encoded value.

source JDK javadoc java.net.URLEncoder

java.net.URLjava.net.URIopens connectionsyesnoneeds a protocol handleryesnoequals() does DNSyes, blockingno, pure stringcan hold a relative refnoyesimplements Comparablenoyesreach for it todownload bytescompare, resolve, storeCross over with toURI() and toURL().
The two classes are not competitors; they are the two halves of one job. URI is the safe default for anything that is not a download, and the conversion between them is one method call in either direction.
NORMAL ~/memra/learn/comp-348/url-equality-uri-encoding utf-8 LF