Von Jamal Hassan, Backend Developer bei Java Fleet Systems Consulting
Mit Einblicken von Nova Trent (Junior Dev) und Elyndra Valen (Senior Dev)

Schwierigkeit: 🟡 Mittel
Lesezeit: 40 Minuten
Voraussetzungen: Java Grundlagen, Try-with-Resources
Kurs: Java Erweiterte Techniken – Tag 7 von 10


📖 Java Erweiterte Techniken – Alle Tage

TagThemaLevel
1Collections – Listen🟢
2Collections – Sets & Maps🟢
3Generics🟡
4Lambda-Ausdrücke🟡
5Functional Interfaces🟡
6Stream-API🟡
→ 7File I/O🟡
8Annotations & Multithreading Basics🟡
9Multithreading – Synchronisation🔴
10Netzwerkprogrammierung🔴

📍 Du bist hier: Tag 7


⚡ Das Wichtigste in 30 Sekunden

Dein Problem: Du musst Konfigurationen laden, Logs schreiben, CSV-Dateien verarbeiten oder Verzeichnisse durchsuchen.

Die Lösung: Die java.nio.file API – modern, sicher und mit Stream-Support.

Heute lernst du:

  • Path und Paths – Dateipfade plattformunabhängig
  • Files – Die Schweizer Taschenmesser-Klasse
  • ✅ Textdateien lesen und schreiben
  • Properties-Dateien laden und speichern
  • ✅ Verzeichnisse erstellen und durchlaufen
  • ✅ Try-with-Resources für sichere Ressourcenverwaltung

Für wen ist dieser Artikel?

  • 🌱 Anfänger: Du lernst Dateien sicher zu lesen und schreiben
  • 🌿 Erfahrene: Properties, Verzeichnis-Streams, Best Practices
  • 🌳 Profis: Performance, große Dateien, Watch Service

👋 Jamal: „Dateien sind überall“

Hi! 👋

Jamal hier für Tag 7. Heute übernehme ich, weil File I/O mein täglich Brot ist – Config-Dateien, Logs, Datenexporte…

Das alte Java (vor 1.7):

// Umständlich und fehleranfällig
File file = new File("config.txt");
FileReader fr = null;
BufferedReader br = null;
try {
    fr = new FileReader(file);
    br = new BufferedReader(fr);
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try { if (br != null) br.close(); } catch (IOException e) {}
    try { if (fr != null) fr.close(); } catch (IOException e) {}
}

Modernes Java (NIO.2):

// Elegant und sicher
Path path = Path.of("config.txt");
Files.readAllLines(path).forEach(System.out::println);

Von 15 Zeilen auf 2. Das ist der Unterschied!


🖼️ Die NIO.2 Architektur

Dateien

Abbildung 1: Path, Files und die wichtigsten Operationen


🟢 GRUNDLAGEN

Path – Der moderne Dateipfad

Path ersetzt das alte File-Objekt und ist plattformunabhängig:

// Path erstellen
Path path1 = Path.of("dokumente", "bericht.txt");
Path path2 = Path.of("/home/user/dokumente/bericht.txt");
Path path3 = Paths.get("config.properties");  // Alternativ

// Pfad-Informationen
System.out.println(path1.getFileName());     // bericht.txt
System.out.println(path1.getParent());       // dokumente
System.out.println(path1.toAbsolutePath()); // /home/.../dokumente/bericht.txt
System.out.println(path1.getNameCount());    // 2

// Pfade kombinieren
Path base = Path.of("/home/user");
Path full = base.resolve("dokumente/bericht.txt");
// /home/user/dokumente/bericht.txt

// Relativen Pfad berechnen
Path from = Path.of("/home/user");
Path to = Path.of("/home/user/dokumente/bericht.txt");
Path relative = from.relativize(to);  // dokumente/bericht.txt

Pfad normalisieren:

Path messy = Path.of("/home/user/../user/./dokumente//bericht.txt");
Path clean = messy.normalize();
// /home/user/dokumente/bericht.txt

Files – Die Utility-Klasse

Files bietet statische Methoden für alle Dateioperationen:

Path path = Path.of("test.txt");

// Prüfungen
boolean exists = Files.exists(path);
boolean isFile = Files.isRegularFile(path);
boolean isDir = Files.isDirectory(path);
boolean readable = Files.isReadable(path);
boolean writable = Files.isWritable(path);

// Datei-Infos
long size = Files.size(path);
FileTime modified = Files.getLastModifiedTime(path);
String mimeType = Files.probeContentType(path);  // "text/plain"

Textdateien lesen

Ganze Datei auf einmal:

// Als Liste von Zeilen
List<String> zeilen = Files.readAllLines(Path.of("daten.txt"));

// Als ein String
String inhalt = Files.readString(Path.of("daten.txt"));

// Mit Encoding
List<String> zeilen = Files.readAllLines(
    Path.of("daten.txt"), 
    StandardCharsets.UTF_8
);

Zeile für Zeile (für große Dateien):

// Mit Stream – lazy, speichereffizient!
try (Stream<String> lines = Files.lines(Path.of("große-datei.txt"))) {
    lines
        .filter(line -> !line.startsWith("#"))  // Kommentare ignorieren
        .map(String::trim)
        .forEach(System.out::println);
}

// Mit BufferedReader
try (BufferedReader reader = Files.newBufferedReader(Path.of("daten.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

Textdateien schreiben

Einfach:

// String schreiben
Files.writeString(Path.of("ausgabe.txt"), "Hallo Welt!");

// Liste von Zeilen
List<String> zeilen = List.of("Zeile 1", "Zeile 2", "Zeile 3");
Files.write(Path.of("ausgabe.txt"), zeilen);

// Anhängen statt überschreiben
Files.writeString(
    Path.of("log.txt"), 
    "Neue Log-Zeile\n",
    StandardOpenOption.APPEND,
    StandardOpenOption.CREATE
);

Mit BufferedWriter:

try (BufferedWriter writer = Files.newBufferedWriter(Path.of("ausgabe.txt"))) {
    writer.write("Zeile 1");
    writer.newLine();
    writer.write("Zeile 2");
    writer.newLine();
}

Try-with-Resources – Niemals vergessen!

KRITISCH: Streams und Reader/Writer müssen immer geschlossen werden!

// ✅ RICHTIG: Try-with-Resources
try (Stream<String> lines = Files.lines(path)) {
    lines.forEach(System.out::println);
}  // Stream wird automatisch geschlossen

// ❌ FALSCH: Resource Leak!
Stream<String> lines = Files.lines(path);
lines.forEach(System.out::println);
// Stream bleibt offen → Datei blockiert!

Mehrere Ressourcen:

try (
    BufferedReader reader = Files.newBufferedReader(input);
    BufferedWriter writer = Files.newBufferedWriter(output)
) {
    String line;
    while ((line = reader.readLine()) != null) {
        writer.write(line.toUpperCase());
        writer.newLine();
    }
}

🟡 PROFESSIONALS

Properties-Dateien – Konfiguration richtig gemacht

Abbildung 2: Properties laden, verwenden, speichern

Properties sind der Standard für Konfigurationsdateien in Java:

config.properties:

# Datenbank-Konfiguration
db.host=localhost
db.port=5432
db.name=myapp
db.user=admin
db.password=geheim123

# Anwendungs-Einstellungen
app.name=MeineApp
app.version=1.0.0
app.debug=true
app.maxConnections=10

Properties laden:

// Aus Datei laden
Properties props = new Properties();
try (InputStream input = Files.newInputStream(Path.of("config.properties"))) {
    props.load(input);
}

// Werte lesen
String host = props.getProperty("db.host");
String port = props.getProperty("db.port", "3306");  // Mit Default
int maxConn = Integer.parseInt(props.getProperty("app.maxConnections", "5"));
boolean debug = Boolean.parseBoolean(props.getProperty("app.debug", "false"));

System.out.println("Verbinde zu " + host + ":" + port);

Properties aus Classpath laden (für JAR-Dateien):

Properties props = new Properties();
try (InputStream input = getClass().getResourceAsStream("/config.properties")) {
    if (input == null) {
        throw new FileNotFoundException("config.properties nicht im Classpath!");
    }
    props.load(input);
}

Properties speichern:

Properties props = new Properties();
props.setProperty("app.name", "MeineApp");
props.setProperty("app.version", "2.0.0");
props.setProperty("last.run", LocalDateTime.now().toString());

try (OutputStream output = Files.newOutputStream(Path.of("config.properties"))) {
    props.store(output, "Anwendungskonfiguration - Nicht manuell bearbeiten!");
}

Ergebnis:

#Anwendungskonfiguration - Nicht manuell bearbeiten!
#Mon Jan 20 14:30:00 CET 2025
app.name=MeineApp
app.version=2.0.0
last.run=2025-01-20T14\:30\:00

Properties iterieren:

// Alle Eigenschaften ausgeben
props.forEach((key, value) -> 
    System.out.println(key + " = " + value));

// Als Stream
props.stringPropertyNames().stream()
    .filter(key -> key.startsWith("db."))
    .forEach(key -> System.out.println(key + " = " + props.getProperty(key)));

Verzeichnisse erstellen und durchlaufen

Verzeichnisse erstellen:

// Ein Verzeichnis
Files.createDirectory(Path.of("neuer-ordner"));

// Verschachtelte Verzeichnisse (wie mkdir -p)
Files.createDirectories(Path.of("pfad/zu/verschachteltem/ordner"));

Verzeichnis-Inhalt auflisten:

// Einfache Liste
try (Stream<Path> stream = Files.list(Path.of("."))) {
    stream.forEach(System.out::println);
}

// Rekursiv durchlaufen
try (Stream<Path> stream = Files.walk(Path.of("projekt"))) {
    stream
        .filter(Files::isRegularFile)
        .filter(p -> p.toString().endsWith(".java"))
        .forEach(System.out::println);
}

// Mit maximaler Tiefe
try (Stream<Path> stream = Files.walk(Path.of("projekt"), 2)) {
    stream.forEach(System.out::println);
}

Dateien finden:

// Alle .txt Dateien finden
try (Stream<Path> stream = Files.find(
        Path.of("dokumente"),
        10,  // Max Tiefe
        (path, attr) -> path.toString().endsWith(".txt") && attr.isRegularFile()
)) {
    stream.forEach(System.out::println);
}

Dateien kopieren, verschieben, löschen

Path quelle = Path.of("original.txt");
Path ziel = Path.of("kopie.txt");

// Kopieren
Files.copy(quelle, ziel);
Files.copy(quelle, ziel, StandardCopyOption.REPLACE_EXISTING);

// Verschieben
Files.move(quelle, ziel);
Files.move(quelle, ziel, StandardCopyOption.ATOMIC_MOVE);

// Löschen
Files.delete(path);                    // Wirft Exception wenn nicht existiert
Files.deleteIfExists(path);            // Gibt false zurück wenn nicht existiert

// Verzeichnis rekursiv löschen
try (Stream<Path> walk = Files.walk(Path.of("zu-loeschen"))) {
    walk.sorted(Comparator.reverseOrder())  // Dateien vor Ordnern!
        .forEach(p -> {
            try { Files.delete(p); } 
            catch (IOException e) { e.printStackTrace(); }
        });
}

Binärdateien

// Binär lesen
byte[] bytes = Files.readAllBytes(Path.of("bild.png"));

// Binär schreiben
Files.write(Path.of("kopie.png"), bytes);

// Mit Streams für große Dateien
try (
    InputStream in = Files.newInputStream(Path.of("große-datei.bin"));
    OutputStream out = Files.newOutputStream(Path.of("kopie.bin"))
) {
    in.transferTo(out);  // Java 9+
}

🔵 BONUS

Temporäre Dateien und Verzeichnisse

// Temporäre Datei
Path tempFile = Files.createTempFile("prefix-", ".tmp");
System.out.println(tempFile);  // /tmp/prefix-12345.tmp

// Temporäres Verzeichnis
Path tempDir = Files.createTempDirectory("myapp-");

// Automatisch löschen bei JVM-Ende
tempFile.toFile().deleteOnExit();

CSV-Dateien verarbeiten

// CSV lesen
Path csv = Path.of("daten.csv");
List<String[]> daten = Files.lines(csv)
    .skip(1)  // Header überspringen
    .map(line -> line.split(";"))
    .toList();

// CSV schreiben
List<String[]> export = List.of(
    new String[]{"Name", "Alter", "Stadt"},
    new String[]{"Max", "25", "Berlin"},
    new String[]{"Anna", "30", "München"}
);

List<String> lines = export.stream()
    .map(arr -> String.join(";", arr))
    .toList();

Files.write(Path.of("export.csv"), lines);

Watch Service – Dateien überwachen

WatchService watchService = FileSystems.getDefault().newWatchService();

Path dir = Path.of("überwacht");
dir.register(watchService, 
    StandardWatchEventKinds.ENTRY_CREATE,
    StandardWatchEventKinds.ENTRY_MODIFY,
    StandardWatchEventKinds.ENTRY_DELETE
);

System.out.println("Überwache " + dir + "...");

while (true) {
    WatchKey key = watchService.take();  // Blockiert
    
    for (WatchEvent<?> event : key.pollEvents()) {
        WatchEvent.Kind<?> kind = event.kind();
        Path fileName = (Path) event.context();
        
        System.out.println(kind + ": " + fileName);
    }
    
    key.reset();
}

💬 Real Talk: File I/O Fallen vermeiden

Java Fleet Büro, Donnerstag 11:00. Nova kämpft mit Dateien.


Nova: „Jamal, mein Programm funktioniert auf meinem Mac, aber auf dem Windows-Server crasht es!“

Path config = Path.of("/home/user/config.properties");  // Unix-Pfad!

Jamal: „Klassiker! Hardcodierte Pfade sind Gift. Nutze relative Pfade oder System-Properties:“

// Besser: Relativ zum Arbeitsverzeichnis
Path config = Path.of("config", "app.properties");

// Oder: Home-Verzeichnis des Users
Path userConfig = Path.of(System.getProperty("user.home"), ".myapp", "config.properties");

// Oder: Aus Classpath laden
getClass().getResourceAsStream("/config.properties");

Nova: „Und warum ist meine Datei nach dem Lesen leer?“

List<String> lines = Files.readAllLines(path);
Files.write(path, processedLines);  // Überschreibt!

Jamal: „Du überschreibst die Originaldatei. Schreib in eine neue Datei, dann umbenennen:“

Path temp = Path.of("daten.tmp");
Files.write(temp, processedLines);
Files.move(temp, path, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);

Elyndra: kommt vorbei „Und vergesst nicht: Files.lines() muss in try-with-resources!“

// ❌ Resource Leak!
Files.lines(path).forEach(System.out::println);

// ✅ Korrekt
try (Stream<String> lines = Files.lines(path)) {
    lines.forEach(System.out::println);
}

❓ FAQ

Frage 1: File oder Path – was soll ich nutzen?

Path (NIO.2) ist der moderne Standard seit Java 7:

  • Plattformunabhängig
  • Bessere API
  • Stream-Support
  • Mehr Funktionen

File nur für Legacy-Code oder APIs, die File erwarten:

Path path = Path.of("datei.txt");
File file = path.toFile();  // Konvertierung wenn nötig

Frage 2: Wie lade ich Properties aus einem JAR?

// FALSCH: Funktioniert nicht im JAR
Properties props = new Properties();
props.load(new FileInputStream("config.properties"));

// RICHTIG: Classpath nutzen
try (InputStream input = getClass().getResourceAsStream("/config.properties")) {
    props.load(input);
}

Die Datei muss in src/main/resources/ liegen!


Frage 3: Encoding-Probleme – warum Umlaute kaputt?

Immer UTF-8 explizit angeben:

// Lesen
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);

// Schreiben
Files.writeString(path, text, StandardCharsets.UTF_8);

// Properties
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
    props.load(reader);
}

Frage 4: Wie vermeide ich FileNotFoundException?

Path path = Path.of("config.properties");

if (Files.exists(path)) {
    // Datei verarbeiten
} else {
    // Default-Konfiguration erstellen
    Files.writeString(path, "# Default Config\napp.name=MyApp");
}

Frage 5: Bernd sagt, Properties seien „veraltet“?

seufz Properties sind nach wie vor der Standard für einfache Konfigurationen:

  • ✅ In der JDK enthalten (keine Abhängigkeit)
  • ✅ Einfaches Format
  • ✅ Gut für Key-Value-Paare
  • ✅ Internationalisierung (ResourceBundle)

Für komplexe Konfigurationen → YAML, JSON, TOML mit entsprechenden Libraries.

🔍 „behind the code“ oder „in my feels“? Die echten Geschichten findest du, wenn du weißt wo du suchen musst…


🎁 Cheat Sheet

🟢 Path erstellen

Path.of("datei.txt")
Path.of("ordner", "datei.txt")
Path.of("/absoluter/pfad")
Paths.get("datei.txt")  // Alternativ

🟡 Dateien lesen

// Alles auf einmal
String text = Files.readString(path);
List<String> lines = Files.readAllLines(path);
byte[] bytes = Files.readAllBytes(path);

// Streaming (große Dateien)
try (Stream<String> lines = Files.lines(path)) { }
try (BufferedReader br = Files.newBufferedReader(path)) { }

🔵 Dateien schreiben

Files.writeString(path, "text");
Files.write(path, lines);
Files.write(path, bytes);

// Optionen
StandardOpenOption.APPEND      // Anhängen
StandardOpenOption.CREATE      // Erstellen wenn nicht existiert
StandardOpenOption.TRUNCATE_EXISTING  // Überschreiben

🟣 Properties

// Laden
Properties props = new Properties();
try (InputStream in = Files.newInputStream(path)) {
    props.load(in);
}

// Lesen
String value = props.getProperty("key");
String value = props.getProperty("key", "default");

// Speichern
props.setProperty("key", "value");
try (OutputStream out = Files.newOutputStream(path)) {
    props.store(out, "Kommentar");
}

🔴 Verzeichnisse

Files.createDirectory(path);
Files.createDirectories(path);  // Rekursiv

try (Stream<Path> s = Files.list(path)) { }      // Inhalt
try (Stream<Path> s = Files.walk(path)) { }      // Rekursiv
try (Stream<Path> s = Files.walk(path, 2)) { }   // Max Tiefe

🎨 Challenge für dich!

🟢 Level 1 – Einsteiger

  • [ ] Lies eine Textdatei und zähle die Zeilen
  • [ ] Schreibe eine Liste von Strings in eine Datei
  • [ ] Prüfe ob eine Datei existiert und lesbar ist

Geschätzte Zeit: 20-30 Minuten

🟡 Level 2 – Fortgeschritten

  • [ ] Lade Properties und gib alle db.* Einträge aus
  • [ ] Finde alle .java Dateien in einem Verzeichnis (rekursiv)
  • [ ] Kopiere alle .txt Dateien von A nach B

Geschätzte Zeit: 45-60 Minuten

🔵 Level 3 – Profi

  • [ ] Implementiere einen Config-Manager mit Default-Werten
  • [ ] Parse eine CSV-Datei in eine Liste von Objekten
  • [ ] Erstelle einen Datei-Backup mit Timestamp

Geschätzte Zeit: 60-90 Minuten


📦 Downloads

ProjektFür wen?Download
tag07-fileio-starter.zip🟢 Mit TODOs⬇️ Download
tag07-fileio-complete.zip🟡 Musterlösung⬇️ Download

🔗 Weiterführende Links

🇩🇪 Deutsch

RessourceBeschreibung
Rheinwerk: Ein-/AusgabeUmfassendes Kapitel

🇬🇧 Englisch

RessourceBeschreibungLevel
Oracle: File I/O TutorialOffizielle Doku🟢
Baeldung: Java NIO2Praxisbeispiele🟡
Baeldung: PropertiesDeep Dive🟡

👋 Geschafft! 🎉

Was du heute gelernt hast:

Path für plattformunabhängige Dateipfade
Files als zentrale Utility-Klasse
✅ Textdateien lesen und schreiben
Properties-Dateien laden und speichern
✅ Verzeichnisse erstellen und durchlaufen
✅ Try-with-Resources für sichere Ressourcenverwaltung

Fragen? Jamal.Hassan@java-developer.online


📖 Weiter geht’s!

← Vorheriger Tag: Tag 6: Stream-API
→ Nächster Tag: Tag 8: Annotations & Multithreading Basics


Tags: #Java #FileIO #NIO2 #Properties #Path #Files #Tutorial

© 2025 Java Fleet Systems Consulting | java-developer.online

Autor

  • Jamal Hassan

    💻 Luca Santoro – Der IT-Ninja

    IT-Support & DevOps Assistant | 29 Jahre | „Ich löse Dinge, bevor sie eskalieren.“

    Wenn bei Java Fleet der Drucker spinnt, das WLAN streikt oder der neue Mitarbeiter kein VPN-Zugriff hat – ist Luca schon unterwegs.
    Er ist der stille Systemretter, der dafür sorgt, dass alle anderen arbeiten können.
    Luca ist nicht laut, nicht hektisch, nicht übertrieben heroisch.
    Er ist einfach da – und das rechtzeitig.

    Seit 2023 unterstützt er das Team im Bereich IT-Support, Netzwerkmanagement und DevOps-Automatisierung.
    Er ist das, was man in der IT selten findet: zuverlässig, gelassen und serviceorientiert – mit technischem Tiefgang und menschlicher Geduld.

    💻 Die Tech-Seite

    Luca denkt in Systemen, nicht in Symptomen.
    Er betreut Hardware, richtet Arbeitsplätze ein, pflegt Benutzerkonten, überwacht Backups und unterstützt das DevOps-Team bei kleineren Deployments.
    Er arbeitet mit Linux, Docker, Active Directory, Ansible und klassischen Office-Netzwerkstrukturen.

    Was ihn besonders macht: Er versteht, dass IT-Support nicht nur Technik ist, sondern Kommunikation.
    Er erklärt, was er tut – klar, freundlich, ohne Fachjargon.
    Und er schreibt sich jeden Fehler auf, um ihn beim nächsten Mal schneller zu beheben.

    „Ich bin kein Feuerwehrmann. Ich bin der, der Rauchmelder installiert.“

    Wenn Franz-Martin über Stabilität redet, meint er oft Systeme, die Luca im Hintergrund pflegt.
    Er hat ein gutes Auge für Details, liebt klare Strukturen und hält Ordnung, wo Chaos droht.

    🌿 Die menschliche Seite

    Luca hat italienisch-deutsche Wurzeln, liebt guten Espresso und hat die entspannte Art eines Menschen, der Probleme ernst nimmt, aber nie dramatisch macht.
    Er ist höflich, hilfsbereit und humorvoll – der Typ Kollege, der leise lacht, wenn etwas schiefläuft, und sagt:

    „Kein Stress. Ich schau’s mir kurz an.“

    Er trägt oft ein leichtes Headset, hört Musik beim Arbeiten und findet in kleinen Routinen seinen Flow.
    Wenn andere nach Feierabend den Laptop zuklappen, prüft er noch schnell den Serverstatus – „nur zur Sicherheit“.

    Cassian sagt: „Er ist die Ruhe im System.“
    Kat nennt ihn „unseren stillen Lifesaver“.
    Und Franz-Martin beschreibt ihn mit einem Augenzwinkern:

    „Luca ist der Grund, warum der Kaffeeautomat läuft – und unser Git-Server auch.“

    🧠 Seine Rolle im Team

    Luca ist das stille Fundament der Java Fleet – derjenige, der sicherstellt, dass alle Systeme, Geräte und Prozesse ineinandergreifen.
    Er ist kein Entwickler im klassischen Sinne, aber ohne ihn gäbe es keine funktionierende Umgebung für die, die entwickeln.
    Seine Arbeit bleibt meist unsichtbar, doch seine Wirkung ist täglich spürbar.

    Er ist der Ansprechpartner, wenn Probleme auftauchen – und noch lieber: bevor sie auftauchen.
    Er dokumentiert, strukturiert, denkt voraus – und erinnert das Team daran, dass Zuverlässigkeit kein Feature ist, sondern eine Haltung.

    ⚡ Superkraft

    Ruhe im System.
    Luca erkennt Fehler, bevor sie eskalieren – und löst sie, bevor sie jemand bemerkt.

    💬 Motto

    „Wenn’s funktioniert und keiner weiß warum – dann hab ich’s repariert.“