Clase Path de Java NIO
Representa rutas del sistema de archivos en Java moderno con java.nio.file.Path y la fábrica Paths.
Path es el reemplazo moderno de java.io.File. Representa una ruta del sistema de archivos — una secuencia ordenada de componentes de nombre, opcionalmente anclada en / o C:\ — y nada más. No lee el archivo. No comprueba si el archivo existe. No bloquea nada. Las operaciones de bytes en disco están en Files (el siguiente capítulo). Path es el sustantivo; Files es el verbo.
Si has usado java.io.File, la diferencia es doble: Path es inmutable (cada operación devuelve un nuevo Path), y separa claramente "la cadena de la ruta" de "lo que hay en disco en esa ruta". La mayoría de las API modernas — Files, FileChannel, sobrecargas de BufferedReader.lines() — toman Path, no File. El código nuevo usa Path.
Construir un 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 URIPath.of(...) es la fábrica moderna; Paths.get(...) es la más antigua y sigue funcionando. Ambas construyen un objeto de ruta sin tocar el sistema de archivos. Path.of("nope/nope/nope") tiene éxito aunque no exista tal archivo.
Path.of une los argumentos varargs con el File.separator de la plataforma — / en Unix, \ en Windows. Esto hace que la ruta literal que escribes sea portable: Path.of("src", "main", "java") construye la ruta correcta en ambos sistemas. En el momento en que escribes Path.of("src/main/java") con barras hardcodeadas, la has hecho exclusiva de Unix sin querer.
Inspeccionar una ruta
Los métodos de acceso a componentes, sobre Path.of("/var/log/app/today.log"):
| Método | Devuelve | Ejemplo |
|---|---|---|
getFileName() | último componente como Path | today.log |
getParent() | todo excepto el último | /var/log/app |
getRoot() | la raíz, o null si es relativa | / |
getNameCount() | número de componentes de nombre | 4 |
getName(int i) | componente i-ésimo | getName(0) → var |
subpath(b, e) | componentes de nombre [b, e) | subpath(1, 3) → log/app |
isAbsolute() | si la ruta tiene raíz | true |
toString() | la cadena formateada según la plataforma | /var/log/app/today.log |
Estos métodos son puros: examinan la lista interna de nombres del objeto de ruta y devuelven fragmentos de ella. Ninguno toca el disco.
resolve, resolveSibling y la trampa del argumento absoluto
resolve(other) es "unir this y other":
Path base = Path.of("/var/log");
base.resolve("app.log"); // /var/log/app.log
base.resolve("app/today.log"); // /var/log/app/today.logLa trampa: si other es absoluto, resolve devuelve other sin cambios:
base.resolve("/etc/hosts"); // /etc/hosts -- base is discarded
base.resolve(Path.of("/etc/hosts")); // same: /etc/hostsEste es el comportamiento documentado y atrapa a todo programador Java una vez. Si aceptas un nombre de archivo de la entrada del usuario y lo resuelves contra un directorio base configurado, un atacante que proporcione "/etc/passwd" aterrizará su ruta absoluta — escapando de la base. Valida o normaliza siempre la entrada externa antes de usar resolve.
resolveSibling(other) reemplaza el último componente:
Path p = Path.of("/var/log/today.log");
p.resolveSibling("yesterday.log"); // /var/log/yesterday.logEs getParent().resolve(other) con una comprobación de nulo incorporada. Útil para "escribir la salida junto a la entrada".
relativize: la operación inversa
Dada una base y un objetivo, base.relativize(target) devuelve la ruta relativa desde base hasta 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); // ../..El contrato: base.resolve(base.relativize(target)) es equivalente a target (módulo normalize). Así es como conviertes un objetivo absoluto en una referencia corta y relativa dentro de un directorio base — útil para líneas de registro, entradas de archivo y URLs.
Ambas rutas deben ser del mismo tipo (ambas absolutas o ambas relativas) y deben provenir del mismo FileSystem. Mezclarlas lanza IllegalArgumentException.
normalize: colapsar . y ..
Los objetos Path permiten componentes . y .. — Path.of("/var/log/../tmp") es un Path válido. normalize() los elimina sintácticamente:
Path.of("/var/log/../tmp").normalize(); // /var/tmp
Path.of("./a/./b/../c").normalize(); // a/c"Sintácticamente" importa: normalize trabaja a nivel de cadena. No pregunta al sistema de archivos si .. apunta realmente a donde sugieren las cadenas. Si /var/log es un enlace simbólico a /tmp/logs, entonces en disco /var/log/.. es /tmp, no /var. normalize() no lo sabe — simplemente elimina el ...
Cuando necesitas la ruta real en disco (enlaces simbólicos resueltos, .. interpretado correctamente), usa toRealPath(), que es una llamada que toca el sistema de archivos:
Path real = Path.of("/var/log/../tmp").toRealPath(); // resolves symlinks, throws if the file doesn't existPara comprobaciones de igualdad de rutas y comparaciones de cadenas, normalize() es lo que quieres. Para "el nombre canónico del archivo en disco en este momento", es toRealPath().
La igualdad es basada en cadenas
path1.equals(path2) compara las rutas como cadenas (componente a componente). No normaliza, no resuelve enlaces simbólicos, no comprueba el sistema de archivos:
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")); // truePara comparar dos rutas como "¿apuntan al mismo archivo?", normaliza ambas y compara, o llama a Files.isSameFile(p1, p2) (toca el sistema de archivos, la única comprobación totalmente correcta). Para ordenar y usar como claves en HashSet, la igualdad por cadena es lo que Path te ofrece; es adecuada para la mayoría de usos, pero no significa "mismo archivo en disco".
Interoperabilidad con File
Path y File se convierten en ambas direcciones:
File f = Path.of("/etc/hosts").toFile();
Path p = new File("/etc/hosts").toPath();Necesitarás esto cuando una API antigua tome File y una nueva tome Path (o viceversa). No almacenes rutas como File; convierte en el límite de la API y mantén Path en tu código.
Ejemplo práctico: join, resolve, relativize, normalize
El programa a continuación recorre cada operación de Path introducida en este capítulo con rutas concretas. La salida hace visible la diferencia entre resolve y resolveSibling, entre normalize y toRealPath, y entre resolve con argumento absoluto y con argumento relativo.
Lo que se puede extraer de la ejecución:
Path.of(\"/var\", \"log\", \"app\", \"today.log\")produjo/var/log/app/today.logen Unix y\\var\\log\\app\\today.logen Windows. Dejar que la fábrica varargs una los componentes es la forma portable; las barras hardcodeadas/o\en la cadena de entrada son la forma no portable. Usa la fábrica.- La línea
resolve(\"/etc/hosts\")descartóbasey devolvió/etc/hosts. Ese es el comportamiento con argumento absoluto y la fuente más frecuente de "pero le di un directorio base, ¿por qué está escribiendo en/etc/hosts?". Valida siempre los nombres de archivo proporcionados por el usuario antes de usarresolve. La forma defensiva esbase.resolve(other).normalize().startsWith(base)— e incluso eso tiene sutiles problemas cuando hay enlaces simbólicos de por medio. base.relativize(target)devolvióapp/today.log. Concatenar eso de vuelta conbase.resolve(...)produjo el objetivo original — la identidad del viaje de ida y vuelta. Úsalo cuando escribas un mensaje de registro o una entrada de archivo que necesita una forma corta y relativa de una ruta absoluta larga.Path.of(\"/var/log/../tmp/./a/b/../c\").normalize()produjo/var/tmp/a/c. La transformación fue puramente a nivel de cadena: cada.eliminado, cada parnombre/..eliminado. No se consultó el sistema de archivos.toRealPathen la siguiente línea sí consultó el sistema de archivos — por eso el resultado fue el nombre canónico, con enlaces simbólicos resueltos, del archivo real en disco. (En macOS verás que la ruta temporal regresa con raíz en/private/var/folders/...en lugar de/var/folders/...:toRealPathsiguió el enlace simbólico real/var→/private/var, algo quenormalizenunca puede hacer.) Dado quetoRealPathverifica cada componente, el directorio intermediosubdel ejemplo debe existir realmente — por eso el programa lo crea primero.- Las dos comprobaciones de
equals:Path.of(\"/var/log\")yPath.of(\"/var\", \"log\")eran iguales (misma secuencia interna de nombres, misma cadena);Path.of(\"/var/log\")yPath.of(\"/var/log/.\")no lo eran (uno tiene un componente.al final, el otro no). La conclusión:equalses una comparación de cadenas. Para "¿apuntan estas dos rutas al mismo archivo?" usaFiles.isSameFile(a, b)del siguiente capítulo — es la única comprobación que pregunta al sistema de archivos.
Qué sigue
Path es el sustantivo. El siguiente capítulo, Clase Files de Java, cubre el verbo — Files, la gigantesca clase de utilidades con operaciones de una línea sobre el sistema de archivos: readString, writeString, createDirectories, copy, move, delete, walk, y más.