URI, URL e URN
Objective
"URI", "URL" e "URN" são usados como sinônimos na fala casual, mas o java.net os modela como coisas genuinamente diferentes, com garantias diferentes: um URI é sintaxe pura — uma string parseada segundo a RFC 3986, sem nenhuma promessa de que ela se refere a algo alcançável; uma URL é um URI que também sabe como localizar um recurso (esquema, host, porta, caminho) e consegue abrir uma conexão com ele; um URN é um URI que nomeia um recurso sem dizer onde encontrá-lo (mailto:, urn:isbn:...). java.net.URI e java.net.URL são classes separadas exatamente por esse motivo, e desde o JDK 20 o próprio JDK direciona você a construir um URI primeiro e convertê-lo para URL só no ponto em que você realmente precisa de um, em vez de construir uma URL diretamente.
Use Cases
- Validar e normalizar um link fornecido pelo usuário (removendo segmentos
.., resolvendo-o contra uma URL base) antes de tentar abrir qualquer conexão com ele. - Construir uma URL a partir de uma base e uma referência relativa — links de paginação, cabeçalhos de redirecionamento
Location, alvos de<a href>em HTML — viaresolve(). - Comparar ou deduplicar links que são textualmente diferentes mas apontam para o mesmo recurso, via
normalize(). - Representar um identificador não obtível por fetch — um ISBN, uma chave de banco de dados, um ID opaco interno — como um
URI(urn:isbn:0-486-27557-4) sem que ele nunca seja confundido com algo em que você pode chamaropenStream(). - Migrar código que usa os construtores deprecados de
new URL(String)sem mudar o comportamento.
Deep Dive
Os três termos, e qual classe modela qual
javaURI uri = new URI("https://darwinsys.com/java/../openbsd/../index.html");
URI normalized = uri.normalize();
System.out.println(normalized); // https://darwinsys.com/index.html
URI base = new URI("https://darwinsys.com");
System.out.println(base.relativize(uri)); // index.html
URL url = normalized.toURL(); // now a locator, ready to connectUm URI só checa sintaxe — new URI("bean:WonderBean") funciona mesmo que nada chamado bean seja um esquema de rede real, porque URI não precisa saber como alcançar nada. Uma URL é mais estrita em um ponto específico e mais permissiva em outro: ela exige um handler de protocolo registrado para o seu esquema (então new URL("bean:WonderBean") lança MalformedURLException), mas historicamente seu parsing por esquema era inconsistente quanto ao que contava como sintaxe válida — o que é o motivo da depreciação descrita abaixo.
Um URN nem é uma classe Java distinta — é só um URI cujo esquema nomeia um recurso em vez de localizá-lo:
javaURI mailto = new URI("mailto:[email protected]");
URI isbn = URI.create("urn:isbn:0-486-27557-4");Nenhum dos dois pode ser aberto como stream — não existe toURL() para mailto: ou urn: sem um handler registrado, porque "como você buscaria isso" não é uma pergunta que um URN responde.
normalize(), relativize(), resolve()
Essas três são as operações que de fato justificam manter URI e URL separados em vez de fazer tudo em uma única classe:
javaURI base = URI.create("https://api.example.com/v1/");
base.resolve("orders/42"); // https://api.example.com/v1/orders/42
base.resolve("/v2/orders/42"); // https://api.example.com/v2/orders/42 (absolute path wins)
base.relativize(
URI.create("https://api.example.com/v1/orders/42")); // orders/42resolve é o que transforma um link relativo de uma página HTML ou de um cabeçalho Location em um absoluto contra a própria URL da página; relativize é o inverso, útil para imprimir links relativos mais curtos quando você já sabe a base da qual todo leitor está partindo. normalize só reescreve segmentos ./.. — não segue redirecionamentos, não checa alcançabilidade, nem busca nada; é manipulação pura de string sobre os componentes já parseados.
Por que new URL(String) está deprecado (desde o JDK 20)
javaURL url = new URL("https://example.com/page"); // deprecated since Java 20Os construtores públicos de URL foram deprecados porque sua checagem de sintaxe variava por esquema e, em alguns pontos, era inconsistente com a RFC — uma string que deveria ter sido rejeitada às vezes não era, dependendo de qual handler estivesse instalado. O parsing de URI é uma checagem de sintaxe RFC 3986 uniforme, independente do esquema, então o caminho de construção recomendado agora é primeiro URI:
javaURL url = URI.create("https://example.com/page").toURL();
// or, for anything with a userinfo/host/port worth validating explicitly:
URI uri = new URI("https://example.com/page");
uri.parseServerAuthority(); // throws URISyntaxException if the authority isn't well-formed
URL strict = uri.toURL();URI.create(String) lança a IllegalArgumentException não verificada em entradas malformadas (útil para literais que você sabe que são válidos); o construtor URI(String) lança a URISyntaxException verificada (útil quando a string vem de fora do seu programa e uma malformada é um caso esperado e recuperável).
Abrindo a conexão
Só uma URL — nunca um URI — consegue de fato buscar alguma coisa:
javaURL url = URI.create("https://darwinsys.com").toURL();
try (InputStream in = url.openStream()) {
// read the resource
}Para qualquer coisa além de um GET-e-leia-o-corpo — cabeçalhos, corpos de POST, timeouts, HTTP/2 — use HttpClient em vez de URLConnection; openStream() continua útil exatamente para o caso "só buscar esses bytes".
Trade-offs
- Um
URIque faz parse com sucesso não é umURIque se refere a algo real.new URI("https://this-domain-does-not-exist.invalid/")funciona — validação de sintaxe e alcançabilidade são duas questões inteiramente diferentes, e sótoURL()mais uma tentativa de conexão de verdade responde a segunda. normalize()é puramente sintático. Ele remove segmentos..e.textualmente; não tem noção de quais segmentos são significativos para o servidor (um..além da raiz, ou um servidor que trata parâmetros de query como significativos para identidade) e não pode substituir uma política de URL canônica do lado do servidor.- Os construtores deprecados de
URLainda funcionam — estão deprecados, não removidos — código existente compila hoje com um aviso, não um erro, então não há um forçador de migração além do próprio aviso; um código-base que nunca olha os warnings do compilador carrega a forma deprecada indefinidamente. - URN como conceito praticamente não tem comportamento em runtime no
java.net. Não existe classeURNnem mecanismo de resolução dedicado — tratar algo como URN é puramente uma escolha de modelagem sobre o que a string significa, garantida por convenção (nunca chamartoURL()nela), não pelo sistema de tipos.