Creación de archivos en Java
Crea archivos y directorios en Java con File.createNewFile, Files.createFile y Files.createDirectories.
"Crear un nuevo archivo vacío" y "crear este árbol de directorios" son dos de las operaciones más simples que ofrece el sistema de archivos, pero Java expone cuatro formas superpuestas de hacer cada una. Las diferencias importan: son la diferencia entre "falla si el archivo existe" y "sobreescribe silenciosamente", entre "mkdir" y "mkdir -p", entre un método heredado que devuelve un boolean y uno moderno que lanza una excepción.
Este capítulo cubre los cuatro métodos de creación:
File.createNewFile()— creación de archivos heredada.File.mkdir()/File.mkdirs()— creación de directorios heredada.Files.createFile(path)— creación moderna atómica "crear o fallar".Files.createDirectory(path)/Files.createDirectories(path)— creación moderna de directorios.
Además de los helpers para archivos temporales (Files.createTempFile, Files.createTempDirectory) y los flags OpenOption que permiten a los escritores crear archivos de forma implícita.
File.createNewFile() — heredado, devuelve un boolean
File f = new File("data/users.txt");
boolean created = f.createNewFile(); // true if it actually created the file
// false if it already existed
// throws IOException if the parent dir is missingEl contrato es verificación y creación atómica: el sistema operativo garantiza que ningún otro proceso puede crear la misma ruta entre la verificación de existencia y la creación. Eso convierte a createNewFile en un primitivo de bloqueo bajo algunos idioms heredados de "archivo PID": if (!f.createNewFile()) throw new IllegalStateException("already running");
Lo que no hace:
- No crea directorios padre faltantes.
new File("does/not/exist/file.txt").createNewFile()lanzaIOException. - Devuelve
false(no lanza excepción) cuando el archivo ya existe.
Si solo necesitas que el archivo exista al final de la llamada, el valor de retorno false es aceptable. Si necesitas que el archivo sea completamente nuevo (semántica de bloqueo), false es tu señal para tomar un camino diferente.
File.mkdir() y File.mkdirs()
new File("logs").mkdir(); // creates "logs" — fails if "." has no perms or parent missing
new File("a/b/c").mkdirs(); // creates "a", "a/b", and "a/b/c" — like `mkdir -p`Ambos devuelven boolean, ambos pierden información sobre por qué ocurrió un fallo. mkdir falla si falta un padre; mkdirs no. Ambos tienen éxito (devuelven true) solo si el directorio fue recién creado — si ya existe, devuelven false. Combinado con el problema de "no hay información del error", este es el tipo de API heredada que se envuelve en un helper:
File dir = new File("data");
if (!dir.exists() && !dir.mkdirs()) throw new IOException("cannot create " + dir);El moderno Files.createDirectories(path) es el reemplazo en una sola línea.
Files.createFile(path) — moderno, lanza excepciones
Files.createFile es el equivalente en java.nio.file de File.createNewFile() con una diferencia importante: lanza excepciones en lugar de devolver un boolean.
Path p = Path.of("data/users.txt");
Files.createFile(p); // creates an empty regular file
// throws FileAlreadyExistsException if it exists
// throws NoSuchFileException if the parent is missingFileAlreadyExistsException es lo que capturas con catch si el resultado de existencia importa; NoSuchFileException es lo que capturas (o evitas con createDirectories) si el padre puede no estar presente. Los tipos de excepción son subclases específicas de IOException, por lo que un catch (IOException e) general sigue funcionando.
Puedes pasar argumentos FileAttribute para establecer permisos POSIX en el momento de la creación en Unix — el uso más común es asegurarse de que los archivos secretos (claves privadas, tokens) se creen con 0600:
import static java.nio.file.attribute.PosixFilePermissions.*;
var attr = asFileAttribute(fromString("rw-------"));
Files.createFile(Path.of("/tmp/secret"), attr); // born with 0600 permissions, atomically(Esa llamada lanza UnsupportedOperationException en Windows, que no tiene modelo de permisos POSIX — protege con una verificación de plataforma si apuntas a ambos sistemas.)
Files.createDirectory versus Files.createDirectories
La misma diferencia que entre mkdir y mkdir -p, pero basada en excepciones:
Files.createDirectory(Path.of("logs")); // one level deep; parent must exist
Files.createDirectories(Path.of("a/b/c")); // creates every missing ancestorcreateDirectory lanza FileAlreadyExistsException si el destino ya existe y no es un directorio; si ya es un directorio, también lanza (no es lo que sueles querer).
createDirectories es la opción más amigable: no hace nada si todos los directorios ya existen, y crea lo que falte en caso contrario. No lanza excepción si la ruta ya existe como directorio. Eso lo hace idempotente — seguro de llamar al inicio sin una verificación de exists().
Archivos y directorios temporales
Para pruebas, espacio de trabajo temporal y "necesito un lugar seguro donde poner esto por unos minutos", el JDK incluye Files.createTempFile y Files.createTempDirectory:
Path scratch = Files.createTempFile("session-", ".log"); // /tmp/session-3829387.log
Path workdir = Files.createTempDirectory("export-"); // /tmp/export-1827392Ambos eligen un nombre único en el directorio temporal del sistema, ambos devuelven un Path a la nueva entrada, y ambos crean la entrada con permisos restrictivos en Unix. El prefijo y sufijo son sugerencias a las que el JDK agrega un valor único — no puedes elegir el nombre exacto (ese es el punto: otro llamador no puede predecirlo y sobreescribir el tuyo).
Los archivos temporales no se eliminan automáticamente. Debes:
- Llamar a
Files.deleteIfExists(path)cuando termines; o - Llamar a
path.toFile().deleteOnExit()para programar una eliminación al apagar la JVM (anulado por kills forzados); o - Abrir el archivo con
StandardOpenOption.DELETE_ON_CLOSEsi solo lo necesitas mientras un stream está abierto.
Los escritores crean archivos de forma implícita
La mayor parte del tiempo no necesitas una llamada a "crear archivo" en absoluto — un escritor crea uno por ti. Files.newBufferedWriter, Files.write y Files.writeString todos aceptan varargs OpenOption... que deciden qué sucede cuando el archivo existe o no:
import static java.nio.file.StandardOpenOption.*;
Files.writeString(path, "hello\n", CREATE, WRITE, TRUNCATE_EXISTING);
Files.writeString(path, "more\n", CREATE, WRITE, APPEND);
Files.writeString(path, "new\n", CREATE_NEW); // fails if file existsCREATE— crea si no existe, de lo contrario abre el existente.CREATE_NEW— crea, lanzaFileAlreadyExistsExceptionsi ya existe. La misma semántica queFiles.createFile.TRUNCATE_EXISTING— borra el contenido del archivo al abrirlo (el valor predeterminado parawriteStringcuando no se agrega contenido).APPEND— escribe al final sin truncar.
El valor predeterminado de Files.writeString (sin opciones) es CREATE, WRITE, TRUNCATE_EXISTING — es decir, "crear o sobreescribir". Files.newBufferedWriter tiene el mismo comportamiento predeterminado. Si quieres semántica de append, debes indicarlo explícitamente.
Un ejemplo práctico: cada método de creación lado a lado
El programa a continuación construye un pequeño árbol desde cero bajo el directorio temporal del sistema usando ambas APIs y varias opciones de apertura. Cada paso imprime lo que cambió; el último bloque muestra qué sucede cuando vuelves a ejecutar una operación contra una ruta que ya existe.
Lo que hay que destacar de la ejecución:
- El primer
legacy.createNewFile()devolviótrue(creado); el segundo devolviófalse(ya existía). Elbooleanno te dice qué ocurrió — tienes que recordar la convención. deep.mkdirs()tuvo éxito la primera vez y devolviófalsela segunda. Esefalsees idéntico a "permiso denegado" o "padre faltante" — exactamente el problema de falta de información de error queFilesresuelve.Files.createFilesobre una ruta existente lanzóFileAlreadyExistsException. El tipo de excepción es específico, por lo que un manejador real puede distinguir "ya existe" de "permiso denegado" sin analizar cadenas de texto.Files.createDirectoriesllamado dos veces seguidas no causó ningún daño la segunda vez. Esa es la propiedad que lo convierte en la elección correcta en el código de inicio: sin guardas, simplemente llámalo.Files.writeString(log, "line 1\n")creó el archivo porqueCREATEestá en las opciones predeterminadas. Las llamadas segunda y tercera usaronAPPENDexplícitamente, y el archivo acumuló tres líneas. La cuarta llamada usóCREATE_NEWy se negó a sobreescribir. Los valores predeterminados están diseñados para el caso de "sobreescribir con nuevo contenido"; tienes que indicar explícitamente el modo append.Files.createTempFile(root, "scratch-", ".tmp")produjo un nombre comoscratch-1827392.tmp— tu prefijo y sufijo, más un fragmento único que la JVM elige para que dos llamadas concurrentes nunca colisionen.- La limpieza recorre
rooten orden inverso para que los archivos y directorios hijos desaparezcan antes que sus padres.Files.deletese niega a eliminar un directorio no vacío; ese orden es cómo se construye unrm -rfmanual.
Qué viene después
Ya puedes crear archivos; el siguiente capítulo, Lectura de archivos en Java, los lee — primero con los helpers modernos en una línea (Files.readString, Files.readAllLines, Files.lines), luego con la pila de decoradores clásica FileReader / BufferedReader / Scanner para que el código más antiguo en los capítulos siguientes tenga una base.