Skip to content

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 →