URL Inspector — Java source
Break any URL into its components - protocol, host, port, path, query params, hash, and credentials. Detects default ports and security at a glance, with a decode toggle for query values. Runs entirely in your browser.
This is the Java implementation — the same logic the interactive tool runs, in a shareable, citable form.
// url-inspector — break any URL into components (protocol, credentials, host, port, path, query params, fragment), detecting default ports and security at a glance. Java (17+) port of src/lib/url-inspector.ts — same logic as this dir's javascript.js; java.net.URI is RFC 3986 and never injects default ports, so an explicit :443 stays visible — exactly the signal the report needs.
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
final class UrlInspector {
/** Well-known default ports per scheme, keyed WHATWG-style with the ':'. */
private static final Map<String, String> DEFAULT_PORTS =
Map.of("http:", "80", "https:", "443", "ftp:", "21", "ws:", "80", "wss:", "443");
/** Schemes that yield a non-opaque origin (others serialise origin as "null"). */
private static final List<String> ORIGIN_SCHEMES = List.of("http", "https", "ws", "wss", "ftp");
private static final String INVALID =
"Invalid URL - could not be parsed (include the scheme, e.g. https://)";
/** A single decoded query parameter, in insertion order (duplicates preserved). */
record UrlParam(String key, String value) {}
/** Flat, serialisable decomposition; null mirrors the TS undefined-when-absent. */
record UrlReport(boolean valid, String protocol, String username, String password,
String host, String hostname, String port, String pathname, String search,
String hash, List<UrlParam> searchParams, String origin, boolean isSecure,
Boolean defaultPort, List<String> warnings) {
static UrlReport invalid(String warning) {
return new UrlReport(false, null, null, null, null, null, null, null, null,
null, List.of(), null, false, null, List.of(warning));
}
}
/**
* Percent-decode a query value; URLDecoder also converts '+' to a space,
* which is exactly the query-string semantic. Malformed escapes throw -
* fall back to the original, like the TS try/catch.
*/
static String decodeParam(String v) {
try {
return URLDecoder.decode(v, StandardCharsets.UTF_8);
} catch (IllegalArgumentException e) {
return v;
}
}
/** Decode a raw query string into ordered pairs, preserving duplicates. */
static List<UrlParam> parseQuery(String raw) {
List<UrlParam> out = new ArrayList<>();
if (raw == null || raw.isEmpty()) return out;
for (String pair : raw.split("&")) {
if (pair.isEmpty()) continue; // "a=1&&b=2" yields an empty pair
int eq = pair.indexOf('=');
String k = eq >= 0 ? pair.substring(0, eq) : pair;
String v = eq >= 0 ? pair.substring(eq + 1) : "";
out.add(new UrlParam(decodeParam(k), decodeParam(v)));
}
return out;
}
/** Parse and decompose a URL into a structured report; never throws. */
static UrlReport inspectUrl(String raw) {
String trimmed = raw == null ? "" : raw.strip();
if (trimmed.isEmpty()) return UrlReport.invalid("URL is empty");
URI uri;
try {
uri = new URI(trimmed);
} catch (URISyntaxException e) {
return UrlReport.invalid(INVALID);
}
// WHATWG validity: an absolute scheme AND a server-based authority are
// both required (getHost() is null for registry-based / scheme-less URIs).
if (uri.getScheme() == null || uri.getHost() == null || uri.getHost().isEmpty())
return UrlReport.invalid(INVALID);
String scheme = uri.getScheme().toLowerCase(Locale.ROOT);
String proto = scheme + ":";
List<String> warnings = new ArrayList<>();
// Credentials — getRawUserInfo() is "user[:pass]", raw (still encoded).
String username = null, password = null;
String info = uri.getRawUserInfo();
if (info != null) {
int colon = info.indexOf(':');
username = colon >= 0 ? info.substring(0, colon) : info;
password = colon >= 0 ? info.substring(colon + 1) : null;
}
if (username != null && !username.isEmpty()) warnings.add("URL contains a username credential");
if (password != null && !password.isEmpty()) warnings.add("URL contains a password credential");
// getHost() keeps IPv6 brackets, like WHATWG url.hostname.
String hostname = uri.getHost();
// getPort() is -1 when no port was written (defaults are NOT injected).
int p = uri.getPort();
String explicitPort = p >= 0 ? Integer.toString(p) : null;
String expected = DEFAULT_PORTS.get(proto);
Boolean defaultPort = null;
if (explicitPort != null) {
defaultPort = explicitPort.equals(expected);
if (defaultPort) warnings.add("Port " + explicitPort + " is the default for " + proto);
}
// host drops a scheme-default port (WHATWG serialisation).
String host = explicitPort != null && !explicitPort.equals(expected)
? hostname + ":" + explicitPort : hostname;
// WHATWG serialises an empty hierarchical path as "/".
String pathname = uri.getRawPath().isEmpty() ? "/" : uri.getRawPath();
List<UrlParam> params = parseQuery(uri.getRawQuery());
if (pathname.equals("/") && (uri.getRawQuery() == null || uri.getRawQuery().isEmpty()) && params.isEmpty())
warnings.add("URL points to the site root (no path or query)");
String origin = ORIGIN_SCHEMES.contains(scheme) ? scheme + "://" + host : null;
return new UrlReport(true, proto,
username == null || username.isEmpty() ? null : username,
password == null || password.isEmpty() ? null : password,
host, hostname, explicitPort, pathname,
uri.getRawQuery() == null || uri.getRawQuery().isEmpty() ? null : "?" + uri.getRawQuery(),
uri.getRawFragment() == null || uri.getRawFragment().isEmpty() ? null : "#" + uri.getRawFragment(),
params, origin,
scheme.equals("https") || scheme.equals("wss"),
defaultPort, warnings);
}
public static void main(String[] args) {
UrlReport r = inspectUrl(
"https://user:pass@example.com:8443/docs/api?q=hello+world&tags=a&tags=b&path=%2Fhome#section");
System.out.println("protocol " + r.protocol() + " secure=" + r.isSecure());
System.out.println("creds " + r.username() + ":" + r.password());
System.out.println("host " + r.host() + " (port " + r.port() + ", default=" + r.defaultPort() + ")");
System.out.println("path " + r.pathname() + " search " + r.search() + " hash " + r.hash());
System.out.println("params " + r.searchParams().stream()
.map(x -> x.key() + "=" + x.value()).reduce((a, b) -> a + ", " + b).orElse(""));
System.out.println("origin " + r.origin());
System.out.println("warnings " + (r.warnings().isEmpty() ? "(none)" : String.join(" | ", r.warnings())));
UrlReport d = inspectUrl("http://example.com:80/");
System.out.println("\nhttp://example.com:80/ -> " + String.join(" | ", d.warnings()));
UrlReport e = inspectUrl("not a url");
System.out.println("'not a url' -> valid=" + e.valid() + " (" + e.warnings().get(0) + ")");
}
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →