WHOIS & RDAP Lookup — Java source
Look up registration data for any domain, IP address, or ASN — straight from your browser to the authoritative RDAP registry over HTTPS. No backend, no database of lookups, fully private.
This is the Java implementation — the same logic the interactive tool runs, in a shareable, citable form.
// whois — RDAP input classification + URL building (WHOIS tool, Java 17 port of src/lib/whois.ts; fetch omitted).
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class WhoisRdap {
record Classified(String kind, String value, String tld) {} // kind: domain|ipv4|ipv6|asn|invalid
static final Pattern ASN_RE = Pattern.compile("^(?:as)?(\\d{1,10})$", Pattern.CASE_INSENSITIVE);
static final Pattern IPV4_RE = Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$");
static final Pattern LABEL_RE = Pattern.compile("^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$", Pattern.CASE_INSENSITIVE);
// Strip scheme, userinfo, path, and optional port down to the bare host.
static String stripToHost(String raw) {
String s = raw.trim().replaceFirst("(?i)^[a-z][a-z0-9+.-]*://", "").replaceFirst("(?i)^(whois|rdap):", "");
if (s.lastIndexOf('@') >= 0) s = s.substring(s.lastIndexOf('@') + 1);
s = s.split("[/?#]", 2)[0];
Matcher bracketed = Pattern.compile("^\\[([0-9a-fA-F:.]+)\\](?::\\d+)?$").matcher(s); // [ipv6](:port)?
if (bracketed.matches()) return bracketed.group(1);
return s.replaceFirst("^([^:\\[\\]]*\\.[^:\\[\\]]*):\\d+$", "$1"); // host:port -> host
}
static boolean isValidIpv4(String host) { // four decimal octets <= 255, no leading zeros
Matcher m = IPV4_RE.matcher(host);
if (!m.matches()) return false;
for (int i = 1; i <= 4; i++) if (Long.parseLong(m.group(i)) > 255 || m.group(i).matches("0\\d")) return false;
return true;
}
static boolean isValidIpv6(String host) { // hex groups of 1-4, at most one "::" collapse
if (!host.contains(":") || !host.matches("[0-9a-fA-F:]+")) return false;
String[] halves = host.split("::", -1);
if (halves.length > 2) return false;
int groups = 0;
for (String half : halves) {
if (half.isEmpty()) continue;
for (String g : half.split(":", -1)) if (g.isEmpty() || g.length() > 4 || !g.matches("[0-9a-fA-F]+")) return false; else groups++;
}
return groups <= 8 && (halves.length == 2 || groups == 8);
}
// Classify a lookup target: ASN (as?\d+) | IPv4 | IPv6 | domain. Never throws.
static Classified classifyInput(String raw) {
if (raw == null) return new Classified("invalid", "", null);
String host = stripToHost(raw);
if (host.isEmpty()) return new Classified("invalid", "", null);
Matcher asn = ASN_RE.matcher(host);
if (asn.matches() && Long.parseLong(asn.group(1)) > 0)
return new Classified("asn", Long.toString(Long.parseLong(asn.group(1))), null);
if (isValidIpv4(host)) return new Classified("ipv4", host, null);
if (isValidIpv6(host)) return new Classified("ipv6", host.toLowerCase(Locale.ROOT), null);
String norm = host.toLowerCase(Locale.ROOT).replaceAll("\\.+$", ""); // trailing root dot(s)
if (!norm.contains(".")) return new Classified("invalid", "", null);
String[] labels = norm.split("\\.", -1);
for (String label : labels) if (!LABEL_RE.matcher(label).matches()) return new Classified("invalid", "", null);
return new Classified("domain", norm, labels[labels.length - 1]);
}
static String objectPath(String kind) { // the RDAP path segment type for a classified input
return switch (kind) { case "domain" -> "domain"; case "ipv4", "ipv6" -> "ip"; case "asn" -> "autnum"; default -> null; };
}
static String buildRdapUrl(String baseUrl, Classified c) { // one slash before the object path
String path = objectPath(c.kind());
return path == null ? "" : baseUrl.replaceAll("/+$", "") + "/" + path + "/" + c.value();
}
static boolean asnInRange(String key, long asn) { // asn.json keys: "lo-hi" or bare
String[] r = key.split("-");
return r.length == 2 ? asn >= Long.parseLong(r[0]) && asn <= Long.parseLong(r[1]) : Long.parseLong(key) == asn;
}
// Bootstrap (dns.json / asn.json): exact TLD match, or "lo-hi" ASN ranges.
static String resolveServer(List<String[]> entries, String tld, Long asn) {
for (String[] entry : entries)
for (String k : entry[0].split(" ")) {
if (tld != null && k.equalsIgnoreCase(tld)) return entry[1];
if (asn != null && asnInRange(k, asn)) return entry[1];
}
return null;
}
public static void main(String[] args) {
List<String[]> dns = List.of(new String[] { "org com", "https://rdap.org" });
List<String[]> asns = List.of(new String[] { "64496-64511", "https://rdap.arin.net/registry" });
for (String q : new String[] { "https://CosmoLabs.org/tools#whois", "AS3356", "192.0.2.1", "not_a_domain" }) {
Classified c = classifyInput(q);
String base = "domain".equals(c.kind()) ? resolveServer(dns, c.tld(), null)
: "asn".equals(c.kind()) ? resolveServer(asns, null, Long.parseLong(c.value())) : null;
String path = objectPath(c.kind());
System.out.printf("%-34s %-8s %s%n", q, path == null ? "invalid" : path, base == null ? "" : buildRdapUrl(base, c));
}
}
}
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 →