CodeGym /Courses /JAVA 25 SELF /Best Practices for Working with Files

Best Practices for Working with Files

JAVA 25 SELF
Level 38 , Lesson 4
Available

1. Error handling: don’t ignore exceptions!

Working with files is always interaction with the outside world, which can be very unpredictable. Disks can fill up, files can disappear, permissions can change, and users can do the unimaginable (for example, run your program from a folder with spaces in its name or from a USB stick that’s about to be yanked out). If your code isn’t ready for this, it risks not just “crashing,” but also leaving the user without the necessary data.

Best practices are not just “fashionable tips,” but a set of time-tested techniques that help avoid the nastiest scenarios: data loss, memory leaks, disclosure of private information, and just plain silly bugs that make it embarrassing to look your colleagues (and especially your users) in the eye later.

Why shouldn’t you write an empty catch?

In Java (and not only there), it’s very tempting to write something like:

try {
    // working with the file
} catch (IOException e) {
    // well, it didn't work out - never mind!
}

This is the worst thing you can do. Such code doesn’t just “swallow” the error — it makes it invisible to both the user and you. As a result, if something goes wrong, you will never know what exactly happened or when.

How to do it right?

  • Log errors: at least print a message to the console or write to a log file.
  • Inform the user: if the error is critical, show a friendly message.
  • Don’t disclose unnecessary details: don’t show the user internal system details (for example, a full stack trace — that’s for developers).

Example:

try {
    List<String> lines = Files.readAllLines(Path.of("data.txt"));
    // data processing
} catch (IOException e) {
    System.err.println("Error reading file: " + e.getMessage());
    // You can write details to a log file
    e.printStackTrace(System.err);
}

Why is it important to catch specific exceptions?
Because different errors require different reactions. For example, if the file is not found, you can prompt the user to choose another file (NoSuchFileException or FileNotFoundException). If there are no permissions, ask to run the program with the necessary rights (AccessDeniedException). If the disk is full, suggest freeing up space (IOException when writing).

2. Permissions and security

Check permissions before operations

Before reading or writing a file, it’s helpful to ensure you have permission to do so. Java provides methods:

  • File.canRead()
  • File.canWrite()

Even if these methods return true, that doesn’t guarantee success — permissions can change at any moment (for example, another process changed them). Therefore, always be prepared for exceptions.

Example:

File file = new File("config.properties");
if (!file.canRead()) {
    System.err.println("No permission to read the file!");
    return;
}
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
    // reading the file
} catch (IOException e) {
    System.err.println("Error while reading: " + e.getMessage());
}

Do not disclose internal details

If your program works with confidential files (for example, passwords), don’t output paths to these files or their contents in errors visible to the user.

3. Do not use relative paths for critical operations

A relative path (new File("data.txt")) is a path relative to the current working directory, which can differ depending on how the program was started (for example, from an IDE or from the command line). This can lead to confusion and errors.

Best practice: use absolute paths for important files or define the working directory explicitly.

Example:

String userHome = System.getProperty("user.home");
Path configPath = Path.of(userHome, "myapp", "config.properties");

4. Working with temporary files and directories

What are temporary files for?

Temporary files are needed for different tasks. Sometimes they are used for intermediate operations: for example, data is first written to a temporary file, and then this file replaces the main one. Another option is that temporary files help store information that isn’t needed after the program finishes and can be safely deleted.

How to create temporary files safely?

Use methods from java.nio.file.Files:

Path tempFile = Files.createTempFile("myapp_", ".tmp");
// ... work with the file
Files.deleteIfExists(tempFile);

Temporary directories

Path tempDir = Files.createTempDirectory("myapp_");

5. Reliability: backups and integrity checks

Use backups when modifying important files

Before overwriting an important file (for example, settings), make a copy of it:

Path config = Path.of("config.properties");
Path backup = Path.of("config.properties.bak");
if (Files.exists(config)) {
    Files.copy(config, backup, StandardCopyOption.REPLACE_EXISTING);
}

If something goes wrong while writing, you can always restore from the backup.

Verify data integrity

For particularly important data, you can use checksums (for example, MD5 or SHA-256). After writing the file, compute a checksum and store it alongside it. When reading, check whether the file has changed.

Example of computing SHA-256 (for cryptography enthusiasts):

import java.security.MessageDigest;
import java.nio.file.Files;
import java.nio.file.Path;

byte[] data = Files.readAllBytes(Path.of("important.dat"));
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data);
// Save the hash to a separate file or compare it when reading

6. Minimize the window between checking and using a file

This is the classic TOCTOU (Time Of Check To Time Of Use) problem we already know: between the moment you checked that the file exists and the moment you started reading it, the file may have disappeared or changed.

So always try to perform the check and the use in the same try block. And make sure to handle exceptions, even if you just checked the file.

Example:

Path filePath = Path.of("data.txt");
if (Files.exists(filePath)) {
    try (BufferedReader reader = Files.newBufferedReader(filePath)) {
        // reading the file
    } catch (IOException e) {
        System.err.println("Error reading file (the file may have disappeared): " + e.getMessage());
    }
}

7. A few more handy tips

Use try-with-resources for all resources
All classes that implement the AutoCloseable interface (which is almost all Java IO/NIO streams) can be used in try-with-resources. This protects against resource leaks.

try (BufferedReader reader = Files.newBufferedReader(Path.of("data.txt"))) {
    // reading
}

Don’t forget to delete temporary files

Files.deleteIfExists(tempFile);

Don’t close a resource twice
If you use try-with-resources, don’t call close() manually — this can lead to errors and duplicate close attempts.

8. Common mistakes when working with files

Error No. 1: Ignoring exceptions.
Writing an empty catch is like catching flies with your hands and letting them go. Always log or at least tell the user what went wrong.

Error No. 2: Not closing streams.
If you forget to close a stream, the file may remain locked, and the system may run out of free descriptors. Use try-with-resources.

Error No. 3: Using relative paths for important files.
Don’t assume the working directory is always what you expect. It’s better to set the path explicitly or use special directories (user.home, java.io.tmpdir).

Error No. 4: Overwriting important files without a backup.
Before you overwrite anything important, make a backup. It will save your nerves and the user’s data.

Error No. 5: Not checking permissions.
Verify that the user has read/write permissions for the required files or directories — otherwise you’ll get unexpected AccessDeniedException.

Error No. 6: TOCTOU window.
Between checking and using a file, someone else may change or delete it. Always handle exceptions, even after a check.

Error No. 7: Leaving temporary files and junk.
After abnormal program termination or errors, temporary files can remain. Don’t forget to delete them, especially if they contain sensitive data.

1
Task
JAVA 25 SELF, level 38, lesson 4
Locked
Creating a Fleeting Note: Temporary Data Storage 📝
Creating a Fleeting Note: Temporary Data Storage 📝
1
Task
JAVA 25 SELF, level 38, lesson 4
Locked
Secret self-destructing message: safely write and erase 💥
Secret self-destructing message: safely write and erase 💥
1
Survey/quiz
Errors when working with files, level 38, lesson 4
Unavailable
Errors when working with files
Errors when working with files
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION