W3docs

Classe Path do Java NIO

Represente caminhos do sistema de arquivos com java.nio.file.Path e a fábrica Paths no Java moderno.

Path é o substituto moderno para java.io.File. Ele representa um caminho no sistema de arquivos — uma sequência ordenada de componentes de nome, opcionalmente com raiz em / ou C:\ — e nada mais. Não lê o arquivo. Não verifica se o arquivo existe. Não bloqueia nada. As operações de bytes no disco estão em Files (o próximo capítulo). Path é o substantivo; Files é o verbo.

Se você já usou java.io.File, a diferença é dupla: Path é imutável (toda operação retorna um novo Path), e ele separa claramente "a string do caminho" de "o que está no disco naquele caminho." A maioria das APIs modernas — Files, FileChannel, sobrecargas de BufferedReader.lines() — aceita Path, não File. Código novo usa Path.

Construindo um Path

Path p = Path.of("logs", "2025", "app.log");         // joins components with the platform separator
Path q = Path.of("/etc/hosts");                       // absolute Unix path
Path r = Paths.get("C:", "Users", "vaz");             // older factory — same behaviour
Path s = Path.of(URI.create("file:///etc/hosts"));    // from a URI

Path.of(...) é a fábrica moderna; Paths.get(...) é a mais antiga e ainda funciona. Ambas constroem um objeto de caminho sem tocar no sistema de arquivos. Path.of("nope/nope/nope") tem sucesso mesmo que nenhum arquivo desse tipo exista.

Path.of une os varargs com o File.separator da plataforma — / no Unix, \ no Windows. Isso torna o caminho que você escreve portável: Path.of("src", "main", "java") constrói o resultado correto em ambas as plataformas. No momento em que você escreve Path.of("src/main/java") com barras codificadas, tornou-o acidentalmente exclusivo do Unix.

Inspecionando um caminho

Os métodos de acesso a componentes, em Path.of("/var/log/app/today.log"):

MétodoRetornaExemplo
getFileName()último componente como Pathtoday.log
getParent()tudo exceto o último/var/log/app
getRoot()a raiz, ou null se relativo/
getNameCount()número de componentes de nome4
getName(int i)i-ésimo componentegetName(0)var
subpath(b, e)componentes de nome [b, e)subpath(1, 3)log/app
isAbsolute()se o caminho tem uma raiztrue
toString()a string formatada pela plataforma/var/log/app/today.log

Esses métodos são puros: analisam a lista interna de nomes do objeto de caminho e retornam fatias dela. Nenhum deles toca o disco.

resolve, resolveSibling e a armadilha do argumento absoluto

resolve(other) é "juntar this e other":

Path base = Path.of("/var/log");
base.resolve("app.log");                              // /var/log/app.log
base.resolve("app/today.log");                        // /var/log/app/today.log

A armadilha: se other for absoluto, resolve retorna other sem alterações:

base.resolve("/etc/hosts");                           // /etc/hosts  -- base is discarded
base.resolve(Path.of("/etc/hosts"));                  // same: /etc/hosts

Esse é o comportamento documentado, e ele pega todo programador Java uma vez. Se você aceita um nome de arquivo da entrada do usuário e o resolve contra um diretório base configurado, um atacante que forneça "/etc/passwd" obtém seu caminho absoluto — escapando do base. Sempre valide ou normalize a entrada externa antes de fazer resolve.

resolveSibling(other) substitui o último componente:

Path p = Path.of("/var/log/today.log");
p.resolveSibling("yesterday.log");                    // /var/log/yesterday.log

É getParent().resolve(other) com uma verificação de null embutida. Útil para "escrever a saída ao lado da entrada."

relativize: o inverso

Dado um base e um alvo, base.relativize(target) retorna o caminho relativo de base para target:

Path base = Path.of("/var/log");
Path target = Path.of("/var/log/app/today.log");
base.relativize(target);                              // app/today.log
target.relativize(base);                              // ../..

O contrato: base.resolve(base.relativize(target)) é equivalente a target (módulo normalize). É assim que você converte um alvo absoluto em uma referência curta e relativa dentro de um diretório base — útil para linhas de log, entradas de arquivo e URLs.

Ambos os caminhos devem ser do mesmo tipo (ambos absolutos ou ambos relativos) e devem vir do mesmo FileSystem. Misturar lança IllegalArgumentException.

normalize: colapsar . e ..

Objetos Path permitem componentes . e ..Path.of("/var/log/../tmp") é um Path válido. normalize() os remove sintaticamente:

Path.of("/var/log/../tmp").normalize();               // /var/tmp
Path.of("./a/./b/../c").normalize();                  // a/c

"Sintaticamente" importa: normalize faz trabalho no nível de string. Ele não pergunta ao sistema de arquivos se .. realmente aponta para onde as strings sugerem. Se /var/log é um link simbólico para /tmp/logs, então no disco /var/log/.. é /tmp, não /var. normalize() não sabe disso — ele apenas apaga o ...

Quando você precisa do caminho real no disco (links simbólicos resolvidos, .. interpretado corretamente), use toRealPath(), que é uma chamada que toca o sistema de arquivos:

Path real = Path.of("/var/log/../tmp").toRealPath(); // resolves symlinks, throws if the file doesn't exist

Para verificações de igualdade de caminhos e comparações de strings, normalize() é o que você quer. Para "o nome canônico do arquivo no disco agora," é toRealPath().

A igualdade é baseada em string

path1.equals(path2) compara os caminhos como strings (componente por componente). Não normaliza, não resolve links simbólicos, não verifica o sistema de arquivos:

Path.of("/var/log").equals(Path.of("/var/log/."));            // false  -- one has a trailing '.' component
Path.of("/var/log/.").equals(Path.of("/var/log/.").normalize()); // false -- normalize() dropped the '.'
Path.of("/var/log").equals(Path.of("/var/log").normalize());  // true   -- already normalized, no change
Path.of("/var/log").equals(Path.of("/var/log"));              // true

Para comparar dois caminhos como "eles apontam para o mesmo arquivo," normalize ambos e compare, ou chame Files.isSameFile(p1, p2) (que toca o sistema de arquivos, a única verificação totalmente correta). Para ordenação e chaves de HashSet, a igualdade de string é o que Path oferece; é adequada para a maioria dos usos, mas não é "mesmo arquivo no disco."

Interoperabilidade com File

Path e File se convertem em ambas as direções:

File f = Path.of("/etc/hosts").toFile();
Path p = new File("/etc/hosts").toPath();

Você precisará disso quando uma API antiga aceita File e uma nova API aceita Path (ou vice-versa). Não armazene caminhos como File; converta na fronteira da API e mantenha Path no seu código.

Um exemplo prático: join, resolve, relativize, normalize

O programa abaixo percorre todas as operações de Path apresentadas neste capítulo com caminhos concretos. A saída torna visível a diferença entre resolve e resolveSibling, entre normalize e toRealPath, e entre resolve com argumento absoluto e com argumento relativo.

java— editable, runs on the server

O que extrair da execução:

  • Path.of(\"/var\", \"log\", \"app\", \"today.log\") produziu /var/log/app/today.log no Unix e \\var\\log\\app\\today.log no Windows. Deixar a fábrica de varargs unir os componentes é a forma portável; barras ou contrabarras codificadas na string de entrada é a forma não portável. Use a fábrica.
  • A linha resolve(\"/etc/hosts\") descartou base e retornou /etc/hosts. Esse é o comportamento com argumento absoluto e a fonte mais frequente de "mas eu forneci um diretório base, por que está escrevendo em /etc/hosts?" Sempre valide nomes de arquivo fornecidos pelo usuário antes de resolve. A forma defensiva é base.resolve(other).normalize().startsWith(base) — e mesmo isso tem armadilhas sutis quando links simbólicos estão envolvidos.
  • base.relativize(target) retornou app/today.log. Concatenar isso de volta com base.resolve(...) produziu o alvo original — a identidade de ida e volta. Use isso quando você está escrevendo uma mensagem de log ou uma entrada de arquivo que precisa de uma forma curta e relativa de um longo caminho absoluto.
  • Path.of(\"/var/log/../tmp/./a/b/../c\").normalize() produziu /var/tmp/a/c. A transformação foi puramente no nível de string: cada . removido, cada par nome/.. removido. O sistema de arquivos não foi consultado. toRealPath na linha seguinte consultou o sistema de arquivos — por isso o resultado foi o nome canônico, com links simbólicos resolvidos, do arquivo real no disco. (No macOS você verá o caminho temporário retornar com raiz em /private/var/folders/... em vez de /var/folders/...: toRealPath seguiu o link simbólico real /var/private/var, algo que normalize nunca pode fazer.) Como toRealPath verifica cada componente, o diretório intermediário sub no exemplo precisa realmente existir — é por isso que o programa o cria primeiro.
  • As duas verificações de equals: Path.of(\"/var/log\") e Path.of(\"/var\", \"log\") foram iguais (mesma sequência interna de nomes, mesma string); Path.of(\"/var/log\") e Path.of(\"/var/log/.\") não foram (um tem um componente . no final, o outro não). A conclusão: equals é uma comparação de strings. Para "esses dois caminhos apontam para o mesmo arquivo?" use Files.isSameFile(a, b) do próximo capítulo — é a única verificação que pergunta ao sistema de arquivos.

O que vem a seguir

Path é o substantivo. O próximo capítulo, Classe Files do Java, cobre o verbo — Files, a enorme classe utilitária de operações de uma linha no sistema de arquivos: readString, writeString, createDirectories, copy, move, delete, walk e o restante.

Prática

Prática
`base` é `Path.of('/srv/uploads')` e `userInput` é a string `'/etc/passwd'` (um valor controlado por um atacante). Seu código faz `base.resolve(userInput)` para calcular o caminho de destino. Qual é o caminho resultante e qual é a lição de segurança?
`base` é `Path.of('/srv/uploads')` e `userInput` é a string `'/etc/passwd'` (um valor controlado por um atacante). Seu código faz `base.resolve(userInput)` para calcular o caminho de destino. Qual é o caminho resultante e qual é a lição de segurança?
Was this page helpful?