Many implementations use exceptions to propagate errors up until the user interface (UI). Here, it would be best if:

  • the user could tell how to recover from the error, and
  • the user could tell how to avoid the error.

The user doesn’t need to know the details about the cause of the error, especially if it includes some implementation detail. For instance, the user will feel helpless, when (s)he is told that a file cannot be written to, if the user has no control over the permissions of the file. If the user doesn’t even know such a file exists, mentioning the file will just cause confusion.

This is where a kind of encapsulation (information hiding) should come into play. I‘m not talking about public/protected/private and the like. What we need to do is to hide the implementation details and give the user some useful feedback. We need to translate the errors occurring in the implementation to something that is readable by a non-developer.

In a very simple application, such as a file move utility, the translation could be performed as follows [i].

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.DirectoryNotEmptyException;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

public class Move {
    public static void main(String [] i_args) {
	if (i_args.length != 2) {
	    System.err.println(
			       "java Move <source> <destination>\n" +
			       "\tMoves file <source> to <destination>");
	    System.exit(-1);
	}

	String sourceName = i_args[0];
	String destinationName = i_args[1];

	File sourceFile = new File(sourceName);
	File destinationFile = new File(destinationName);
                
	if (!sourceFile.exists()) {
	    System.err.println("ERROR: Source does not exist.");
	    System.exit(-1);
	}

	if (!sourceFile.isFile()) {
	    System.err.println("ERROR: Source is not a file.");
	    System.exit(-1);
	}

	try {
	    Files.move(sourceFile.toPath(),destinationFile.toPath(), REPLACE_EXISTING);
	} catch (DirectoryNotEmptyException exception) {
	    System.err.println("ERROR: The destination is a non-empty directory.");
	    System.exit(-1);
	} catch (IOException exception) {
	    System.err.println("ERROR: Could not read/write file, because of an I/O error.");
	    System.exit(-1);
	} catch (Throwable thrown) {
	    System.err.println("ERROR: " + thrown);
	    System.exit(-1);
	}
    }
}

In this example, exceptions are caught [ii] and then printed to the screen. See how the messages provide some context to the error in human-readable form. If the program just showed the raw error message thrown from Files.move(), the user might be shown a cryptic message such as:

 Unexpected error: java.nio.file.NoSuchFileException: noneExist

Since the caller knows why File.move() is being called, it can provide more helpful messages. In the source code above, the only case when the message in the exception is shown directly is when the exception (Throwable) was not anticipated.


[i] Input arguments are prefixed with “i_”.

[ii] If you’ve ever tried using Files.move(), or any other standard function, you will also discover that not all the exceptions are listed in the API document. The java.nio.file.NoSuchFileException is just one example. It’s lumped into one superclass, java.lang.IOException. The API document does need to be improved.

Contents

Background

Review of Exception Handling Basics

The Requirements

The Three-tiered Exception Handling Architecture

Summary