FileNotFoundException in Java
  • 16-Mar-2023
Jura Gorohovsky
Author Jura Gorohovsky
Share
FileNotFoundException in Java

FileNotFoundException in Java

Jura Gorohovsky
Jura Gorohovsky
16-Mar-2023

Let’s take a look at FileNotFoundException, a common Java exception that needs to be handled when a Java application reads from or writes to files.

FileNotFoundException: quick facts

  • Qualified class name: java.io.FileNotFoundException.
  • Exception type: checked exception.
  • Summary: signals failure to open a file because the file doesn’t exist or permissions are insufficient.
  • Subclass of: java.io.IOException.

When and why does FileNotFoundException usually occur in Java?

FileNotFoundException gets thrown when a Java application is unable to open a file at the path that it’s given. This can happen for a few reasons:

  • The specified file does not exist.
  • The specified file is in fact a directory.
  • The specified file exists but the Java application can’t access it due to insufficient permissions.

FileNotFoundException is usually thrown when you pass a File object to an object of type FileInputStream, Scanner or FileReader (used to read from files), FileOutputStream (used to write to files), or RandomAccessFile (can both read and write).

Is FileNotFoundException a checked or unchecked exception?

FileNotFoundException is a checked exception, which means that the Java compiler requires you to handle it. Any time you use a method that can potentially throw FileNotFoundException, you need to either:

  • Handle this exception using a try/catch block, or
  • Add a throws clause to your method to delegate the responsibility of handling this exception to callers of your method.

If you don’t do any of this, Java will prevent you from compiling your application.

FileNotFoundException extends IOException which, in turn, is a subtype of Exception, the base type of all checked exceptions in Java. Here’s the class hierarchy for FileNotFoundException:

Throwable (java.lang)
    Exception (java.lang)
        IOException (java.io)
            FileNotFoundException (java.io)

IOException is the most generic exception in a large group of exceptions that express input/output and networking errors in Java applications. Other subtypes of IOException besides FileNotFoundException include EOFException, MalformedInputException, FileSystemException, MalformedURLException, UnknownHostException, and many others.

FileNotFoundException example: using a FileReader

Since FileNotFoundException is a checked exception, the Java compiler won’t let you get away with not handling it. For example, what happens if you try to compile the code below?

private static void tryReadFile() {
    File myFile = new File("file.txt");
    FileReader reader = new FileReader(myFile);
}

Compilation will fail with the following error message:

../FileAccessSample.java:14:29
java: unreported exception java.io.FileNotFoundException; must be caught or declared to be thrown

That’s because FileReader uses the throws clause to delegate handling of FileNotFoundException to its callers:

public FileReader(File file) throws FileNotFoundException {
    super(new FileInputStream(file));

What do you do about it? Let’s see how you can catch this exception, or even a group of related exceptions, right in this method, or use the throws clause to do it elsewhere.

How to catch FileNotFoundException

To address the compilation error shown above, you need to either put the FileReader call into a try/catch block or include a throws clause in your method declaration. Let’s consider the two scenarios.

Handling FileNotFoundException with the throws clause

One way to solve the compilation error shown above would be to add a throws clause to the method:

private static void tryReadFile() throws FileNotFoundException {
    File myFile = new File("file.txt");
    FileReader reader = new FileReader(myFile);
}

(Notice that since FileNotFoundException derives from IOException, a throws IOException declaration will also work.)

Doing so would make callers of this method responsible for handling the FileNotFoundException. That is, unless this method is unused, trying to compile again will result in a similar compiler error that relocates to wherever you’re calling your method from:

../FileAccessSample.java:9:56
java: unreported exception java.io.FileNotFoundException; must be caught or declared to be thrown

You could then add a similar throws clause to the calling method, then to its calling method, and continue doing so all the way up to your application’s main() method. If the main() method gets FileNotFoundException added to its throws clause as well, the compiler will give up and let you build the application. However, as soon as the exception actually occurs, your application will simply exit without a chance of recovery if the file isn’t there. Technically, you can do this, but leaving your application without error handling code is a really bad idea.

Only use the throws clause when you have a strategy of handling exceptions up the call stack in your application. If you’re writing a library, use the throws clause if you believe that its users will benefit from flexibility in handling error conditions more than they will struggle from having to write extra exception handling code.

In Java frameworks such as Spring REST services, you can use the throws clause to propagate FileNotFoundException and other exceptions all the way to the REST controller and handle them there, transforming internal exception details to clear error messages that API consumers can understand.

Handling FileNotFoundException using the try/catch block

If you want to handle FileNotFoundException right there in your method, you need to put the FileReader constructor into a try/catch block:

private static void tryReadFile() {
    File myFile = new File("file.txt");
    try {
        FileReader reader = new FileReader(myFile);
    } catch (FileNotFoundException exception) {
        // Exception handling code goes here
    }
}

How exactly you handle FileNotFoundException here may vary. For a console application, it may be enough to simply print the exception’s stack trace:

catch (FileNotFoundException exception) {
    e.printStackTrace();
}

If you’re logging errors instead of relying on console output, you can log the exception and specify relevant details, such as the path to the file that your application failed to read:

catch (FileNotFoundException exception) {
    LOGGER.log(Level.SEVERE, MessageFormat.format("Could not read file {0}. {1}.",
        myFile.getAbsolutePath(),
        myFile.exists()
                ? "The file is reported as existing, check access permissions"
                : "The file does not exist"),
        exception);
}

Alternatively, you could recover by creating a new file:

catch (FileNotFoundException exception) {
    try {
        myFile.createNewFile();
    } catch (IOException ioException) {
        throw new RuntimeException(MessageFormat.format("Unable to create file {0}.",
                                                        myFile.getAbsolutePath()),
                                   ioException);
    }
}

Instead of only creating a new file when you handle FileNotFoundException, you could as well check that the file exists in the try part of the block:

private static void tryReadFile() {
    File myFile = new File("file.txt");
    try {
        if (!myFile.exists()) {
            myFile.createNewFile();
            myFile = addDefaultContent(myFile);
        }
        FileReader reader = new FileReader(myFile);
    } catch (IOException exception) {
        LOGGER.log(Level.SEVERE,
                MessageFormat.format("Could not read or create file {0}.",
                                     myFile.getAbsolutePath()),
                exception);
    }
}

Notice that in this case, the catch block targets IOException, and that’s due to inheritance:

  • createNewFile() throws an IOException.
  • new FileReader() throws a FileNotFoundException, which is a subtype of IOException.

The Java compiler wants you to catch both of these checked exceptions, but since IOException is the parent of FileNotFoundException, you only need to catch IOException, which will also handle FileNotFoundException using the same exception handling code.

In case you want to handle IOException and FileNotFoundException in two different ways, you can add two catch blocks. The first block should target the more specific FileNotFoundException and the second block should target the more generic IOException:

try {
    // Try do something
} catch (FileNotFoundException exception) {
    // FileNotFoundException is handled first.
}
catch (IOException exception) {
    // IOException is handled second.
}

If you attempt to catch the more generic IOException in the first block, it will also catch FileNotFoundException, and the second catch block will become unused, causing a compiler error:

../FileAccessSample.java:94:9
java: exception java.io.FileNotFoundException has already been caught

One more thing to know about handling FileNotFoundException is that as you move your code around, you may end up catching this exception where it can no longer occur. If this happens, you’ll see a compiler error:

../FileAccessSample.java:104:11
java: exception java.io.FileNotFoundException is never thrown in body of corresponding try statement

Here’s an example of code that can cause this compiler error:

private static void tryReadFile() {
    File myFile = new File("existingFile.txt");
    try {
        FileReader reader = getFileReader(myFile);
        BufferedReader bufferedReader = new BufferedReader(reader);
        System.out.println(bufferedReader.lines()
                           .collect(Collectors.joining(System.lineSeparator())));
    } catch (FileNotFoundException exception) {
        // Exception handling code goes here
    }
}

Instead of instantiating a FileReader object right here in the try block, this code calls getFileReader(myFile) to get the object elsewhere. As a result, a FileNotFoundException can only occur (and should be handled) inside getFileReader(), but not in the method that makes a call to it. In this case, you should remove the catch block targeting FileNotFoundException that the compiler complains about.

How to throw FileNotFoundException

You rarely want to throw a FileNotFoundException, but if you do, you can use one of the following constructors:

  1. A parameterless constructor: throw new FileNotFoundException();
  2. A constructor that lets you specify the reason for throwing the exception: throw new FileNotFoundException("Here's what happened here");

Since it’s always wise to provide details for troubleshooting, using the latter constructor is the better choice.

Unlike many other Java exceptions, FileNotFoundException doesn’t provide a constructor that would allow passing a different exception as the cause.

Production debugging isn’t scary with Lightrun

Properly handling exceptions is one thing that you as a developer can do to ensure a smooth ride in production for your Java applications. Still, let’s face it: any non-trivial application will have bugs. You’re lucky if you can reproduce a bug in a local environment, debug and happily push a verified fix.

What if you can’t? Debugging remotely is tricky: you need to rely on existing logging, repeatedly redeploy updates with more logs and attempted fixes, and you’re even unable to set a proper breakpoint because you can’t afford to halt a production environment.

Take a look at Lightrun: our next-gen remote debugger for your production environment. With Lightrun, you can inject logs without changing code or redeploying, and add snapshots: breakpoints that don’t stop your production application. Lightrun supports Java, .NET, Python and Node.js applications, integrates with IntelliJ IDEA and VS Code. Create a Lightrun account and check for yourself!

Share

It’s Really not that Complicated.

You can actually understand what’s going on inside your live applications.

Try Lightrun’s Playground

Lets Talk!

Looking for more information about Lightrun and debugging?
We’d love to hear from you!
Drop us a line and we’ll get back to you shortly.

By clicking Submit I agree to Lightrun’s Terms of Use.
Processing will be done in accordance to Lightrun’s Privacy Policy.