Leer y escribir ficheros en Java
En esta lección
Todo lo que guarda un programa en variables desaparece al cerrarlo. Para que los datos sobrevivan (la lista de tareas, las notas de una clase, el registro de ventas de una tienda) hay que guardarlos en ficheros. En esta lección aprenderás a leer y escribir ficheros de texto con las clases modernas Path y Files, a trabajar con carpetas, a manejar los errores de entrada y salida y, como ejemplo completo, a leer un CSV, convertirlo en objetos y escribir un informe.
Path y Files: las herramientas modernas
Java tiene dos generaciones de clases para ficheros. Las antiguas (File, FileReader, FileWriter) siguen funcionando y las verás en muchos apuntes, pero desde Java 7 existe el paquete java.nio.file, más cómodo y con mejores mensajes de error:
Pathrepresenta una ruta a un fichero o carpeta. No abre nada: solo describe dónde está.Fileses una clase con métodosstaticque hacen el trabajo: leer, escribir, copiar, borrar, crear carpetas…
import java.nio.file.Files;
import java.nio.file.Path;
Path ruta = Path.of("datos", "tareas.txt");
System.out.println(ruta); // datos/tareas.txt
System.out.println(ruta.getFileName()); // tareas.txt
System.out.println(ruta.getParent()); // datos
System.out.println(Files.exists(ruta)); // true
Path.of("datos", "tareas.txt") une las partes con el separador correcto del sistema: / en Linux y macOS, \ en Windows. Por eso es mejor que escribir la ruta a mano.
Rutas relativas y absolutas
- Una ruta relativa (
datos/tareas.txt) se busca a partir de la carpeta desde la que se ejecuta el programa. En un IDE suele ser la carpeta raíz del proyecto. - Una ruta absoluta empieza desde la raíz del disco:
/home/ana/datos/tareas.txtoC:\Users\Ana\datos\tareas.txt.
Usa rutas relativas siempre que puedas: así el programa funciona en cualquier ordenador. Si no sabes dónde está buscando Java, imprime ruta.toAbsolutePath().
Excepciones de entrada y salida: IOException
Casi todos los métodos de Files pueden lanzar IOException: el fichero no existe, no tienes permisos, el disco está lleno… Es una excepción checked, así que el compilador te obliga a tratarla. Como viste en excepciones, tienes dos opciones: capturarla o declararla con throws.
try {
List<String> lineas = Files.readAllLines(Path.of("no-existe.txt"));
} catch (NoSuchFileException e) {
System.out.println("No existe el fichero: " + e.getMessage());
} catch (IOException e) {
System.out.println("Error de lectura: " + e.getMessage());
}
// No existe el fichero: no-existe.txt
NoSuchFileException es una subclase de IOException, por eso va primero. En los ejemplos siguientes supondremos que el método tiene throws IOException para no repetir el try en cada uno.
Leer ficheros de texto
Partimos de un fichero datos/tareas.txt con este contenido:
Comprar pan
Estudiar Java
Ir al gimnasio
readAllLines: todas las líneas en una lista
List<String> tareas = Files.readAllLines(ruta);
System.out.println(tareas.size()); // 3
for (String t : tareas) {
System.out.println("- " + t);
}
// - Comprar pan
// - Estudiar Java
// - Ir al gimnasio
Devuelve una lista con una línea por elemento, sin los saltos de línea. Es la forma más cómoda para ficheros pequeños y medianos.
readString: todo el contenido en un String
String contenido = Files.readString(ruta);
System.out.print(contenido);
// Comprar pan
// Estudiar Java
// Ir al gimnasio
Útil cuando quieres el texto completo, por ejemplo para buscar una palabra o contar caracteres.
lines: un stream de líneas
Para ficheros muy grandes (un registro de millones de líneas) no conviene cargar todo en memoria. Files.lines devuelve un stream que va leyendo poco a poco. Como mantiene el fichero abierto, se usa con try-with-resources:
try (Stream<String> lineas = Files.lines(ruta)) {
long conJava = lineas.filter(l -> l.contains("Java")).count();
System.out.println(conJava); // 1
}
BufferedReader: leer línea a línea
Es la forma clásica, y la que encontrarás en código antiguo. readLine() devuelve la siguiente línea o null cuando se acaba el fichero:
try (BufferedReader lector = Files.newBufferedReader(ruta)) {
String linea;
int numero = 1;
while ((linea = lector.readLine()) != null) {
System.out.println(numero + ": " + linea);
numero++;
}
}
// 1: Comprar pan
// 2: Estudiar Java
// 3: Ir al gimnasio
El buffer hace que se lean bloques grandes del disco de una vez en lugar de carácter a carácter, lo que es mucho más rápido. El try-with-resources garantiza que el fichero se cierra aunque haya un error.
Escribir ficheros de texto
writeString y write
Path notas = Path.of("datos", "notas.txt");
Files.writeString(notas, "Primera línea\n");
Files.write(notas, List.of("uno", "dos", "tres")); // una línea por elemento
System.out.println(Files.readAllLines(notas)); // [uno, dos, tres]
writeStringescribe un texto tal cual (tú pones los\n).writecon una lista escribe cada elemento en una línea.- Si el fichero no existe, lo crean. Si existe, lo sobrescriben por completo.
Añadir al final: APPEND
Para no borrar lo que había (un registro de actividad, por ejemplo), pasa la opción StandardOpenOption.APPEND:
Files.writeString(notas, "Primera línea\n");
Files.writeString(notas, "Segunda línea\n", StandardOpenOption.APPEND);
System.out.print(Files.readString(notas));
// Primera línea
// Segunda línea
Cuidado: sin
APPEND, cada escritura borra el contenido anterior. Es un error muy fácil de cometer y los datos perdidos no se recuperan.
BufferedWriter
Si vas a escribir muchas líneas poco a poco, por ejemplo dentro de un bucle, usa un BufferedWriter:
Path registro = Path.of("datos", "registro.txt");
try (BufferedWriter escritor = Files.newBufferedWriter(registro)) {
escritor.write("Inicio de sesión");
escritor.newLine();
escritor.write("Cierre de sesión");
escritor.newLine();
}
Es importante cerrarlo (el try-with-resources lo hace por ti): lo escrito se guarda en el buffer y solo se vuelca al disco al cerrar o al llamar a flush(). Si no se cierra, puedes perder las últimas líneas.
FileWriter y PrintWriter
En muchos apuntes verás la forma clásica con FileWriter. El segundo argumento true indica que se añade al final; envuelto en un PrintWriter puedes usar println y printf como en la consola:
try (FileWriter fw = new FileWriter("datos/registro.txt", StandardCharsets.UTF_8, true);
PrintWriter pw = new PrintWriter(fw)) {
pw.println("Nueva entrada");
pw.printf("Total: %d tareas%n", 3);
}
Codificación: UTF-8
Un fichero de texto se guarda como bytes, y la codificación dice cómo convertir esos bytes en letras. Si se escribe con una y se lee con otra, las tildes y la ñ salen como ñ o como un rombo negro con una interrogación.
- Los métodos de
Files(readAllLines,readString,writeString,newBufferedReader…) usan UTF-8 por defecto. - Desde Java 18,
FileReaderyFileWritertambién usan UTF-8 por defecto. En versiones anteriores dependían del sistema, por eso conviene indicarlo siempre:new FileWriter(ruta, StandardCharsets.UTF_8).
Si un fichero viene de otro programa con otra codificación (por ejemplo, un CSV antiguo de Excel en Windows), puedes indicarla: Files.readAllLines(ruta, Charset.forName("windows-1252")).
Trabajar con carpetas
Path carpeta = Path.of("informes", "2025", "junio");
System.out.println(Files.exists(carpeta)); // false
Files.createDirectories(carpeta); // crea todas las que falten
System.out.println(Files.isDirectory(carpeta)); // true
createDirectories crea también las carpetas intermedias y no da error si ya existen. createDirectory (singular) solo crea una y lanza FileAlreadyExistsException si ya estaba.
Para listar el contenido de una carpeta se usa Files.list, que devuelve un stream y también va con try-with-resources:
try (Stream<Path> contenido = Files.list(Path.of("datos"))) {
contenido.sorted().forEach(p -> System.out.println(p.getFileName()));
}
// alumnos.csv
// notas.txt
// registro.txt
// tareas.txt
Otras operaciones útiles:
| Método | Qué hace |
|---|---|
Files.exists(ruta) | Comprueba si existe |
Files.isDirectory(ruta) | Comprueba si es una carpeta |
Files.size(ruta) | Tamaño en bytes |
Files.copy(origen, destino) | Copia un fichero |
Files.move(origen, destino) | Mueve o renombra |
Files.delete(ruta) | Borra (error si no existe) |
Files.deleteIfExists(ruta) | Borra si existe |
Ejemplo completo: de CSV a objetos y a un informe
Un CSV es un fichero de texto con datos en forma de tabla: una fila por línea y los campos separados por un carácter, normalmente , o ; (en España se usa mucho ; porque la coma es el separador decimal). Es el formato que exportan Excel, Google Sheets y muchas bases de datos.
Tenemos datos/alumnos.csv:
nombre;grupo;nota
Lucía;1B;8.5
Mario;1A;6.0
Aitana;1B;9.2
Hugo;1A;4.5
Leer el CSV y crear objetos
Cada línea se convierte en un objeto Alumno (un record). Los pasos son: leer las líneas, saltar la cabecera, partir cada línea con split y convertir cada campo a su tipo:
record Alumno(String nombre, String grupo, double nota) {}
static List<Alumno> leerAlumnos(Path ruta) throws IOException {
List<Alumno> alumnos = new ArrayList<>();
List<String> lineas = Files.readAllLines(ruta);
for (int i = 1; i < lineas.size(); i++) { // empieza en 1: salta la cabecera
String linea = lineas.get(i).trim();
if (linea.isEmpty()) {
continue; // ignora líneas en blanco
}
String[] campos = linea.split(";");
alumnos.add(new Alumno(campos[0], campos[1], Double.parseDouble(campos[2])));
}
return alumnos;
}
List<Alumno> alumnos = leerAlumnos(Path.of("datos", "alumnos.csv"));
alumnos.forEach(System.out::println);
// Alumno[nombre=Lucía, grupo=1B, nota=8.5]
// Alumno[nombre=Mario, grupo=1A, nota=6.0]
// Alumno[nombre=Aitana, grupo=1B, nota=9.2]
// Alumno[nombre=Hugo, grupo=1A, nota=4.5]
Si una nota viniera como 8,5 (con coma), Double.parseDouble lanzaría NumberFormatException. Podrías sustituir la coma antes: campos[2].replace(',', '.').
Escribir un informe
Ahora generamos un informe de texto con la nota de cada alumno y un resumen:
List<String> informe = new ArrayList<>();
informe.add("INFORME DE NOTAS");
informe.add("================");
double suma = 0;
int aprobados = 0;
for (Alumno a : alumnos) {
String estado = a.nota() >= 5 ? "Aprobado" : "Suspenso";
informe.add(a.nombre() + " (" + a.grupo() + "): " + a.nota() + " - " + estado);
suma += a.nota();
if (a.nota() >= 5) {
aprobados++;
}
}
informe.add("");
informe.add("Media: " + suma / alumnos.size());
informe.add("Aprobados: " + aprobados + " de " + alumnos.size());
Files.createDirectories(Path.of("informes"));
Files.write(Path.of("informes", "notas.txt"), informe);
Contenido de informes/notas.txt:
INFORME DE NOTAS
================
Lucía (1B): 8.5 - Aprobado
Mario (1A): 6.0 - Aprobado
Aitana (1B): 9.2 - Aprobado
Hugo (1A): 4.5 - Suspenso
Media: 7.05
Aprobados: 3 de 4
Este patrón (leer datos de un fichero, procesarlos y escribir un resultado) es la base de muchísimos programas reales. Cuando los datos crecen o varias personas deben usarlos a la vez, el siguiente paso es una base de datos.
Consejo: para CSV reales con comillas, campos que contienen el separador o saltos de línea dentro de un campo, un simple
splitno basta. En proyectos profesionales se usan bibliotecas como OpenCSV o Apache Commons CSV.
Errores frecuentes
- No tratar
IOException. El código no compila: captúrala o añadethrows IOException. - Olvidar
APPENDy sobrescribir el fichero en cada escritura. - No cerrar los recursos. Usa siempre try-with-resources con
BufferedReader,BufferedWriter,Files.linesyFiles.list. - Rutas mal escritas o relativas a una carpeta distinta de la que crees. Imprime
ruta.toAbsolutePath()para comprobarlo. - Escribir rutas con
\a mano ("datos\tareas.txt"):\tes un tabulador. UsaPath.of("datos", "tareas.txt"). - Mezclar codificaciones y obtener tildes rotas. Trabaja siempre en UTF-8.
- Cargar con
readAllLinesun fichero enorme. Para eso estáFiles.lines. - Olvidar la cabecera del CSV y obtener
NumberFormatExceptional convertir"nota"en número.
Resumen
| Tarea | Código |
|---|---|
| Crear una ruta | Path.of("datos", "tareas.txt") |
| Comprobar si existe | Files.exists(ruta) |
| Leer todas las líneas | Files.readAllLines(ruta) |
| Leer todo el texto | Files.readString(ruta) |
| Leer como stream | try (Stream<String> s = Files.lines(ruta)) { ... } |
| Leer línea a línea | Files.newBufferedReader(ruta) + readLine() |
| Escribir (sobrescribe) | Files.writeString(ruta, texto), Files.write(ruta, lista) |
| Añadir al final | Files.writeString(ruta, texto, StandardOpenOption.APPEND) |
| Escribir mucho | Files.newBufferedWriter(ruta) + write / newLine |
| Crear carpetas | Files.createDirectories(ruta) |
| Listar carpeta | try (Stream<Path> s = Files.list(carpeta)) { ... } |
| Error de E/S | IOException (checked) |
En la siguiente lección darás ese paso: conectar Java con una base de datos usando JDBC.
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.