[Java] Effective Java Use checked exceptions for recoverable states and runtime exceptions for programming errors

3 minute read

It is a unique interpretation of Effective Java. I tried to interpret item 70 of the 3rd edition by writing the code myself.

Roughly

“Recoverable” means that the current thread can be returned to another control without stopping even if an exception occurs. Since the method implementer is aware of the possibility that an exception will occur, output a checked exception and force the method caller to handle it. A “programming error” is one in which a method is used in a way prohibited by the implementer and the current thread must be stopped. Causes a runtime exception to be printed (which should generally not be caught).

Example requiring a check exception

Consider the following service layer method.

  • Purchase a book with the specified ID
  • Reference table is “book” “bank account” “purchased books” table
  • If there is enough money to buy the book in the “bank account”, add the book to the “purchased books” and reduce the balance of the “bank account”
  • If there is not enough money to purchase the book in the “bank account”, the exception of balance shortage is output.

*Table definitions are omitted.

This purchase method


     /**
     * Purchase a book with the specified ID
     * Add purchased books to the purchased books table to reduce the balance
     * Output exception if balance is insufficient
     *
     * @param bookId Book ID to purchase
     * @param userId User ID
     * @throws InsufficientFundsException Out of balance exception
     */
    public void buyBook(String bookId, int userId) throws InsufficientFundsException {

        // get the price of the book to buy
        Book selectedBook = bookDao.selectByBookId(bookId);
        int bookPrice = selectedBook.getPrice();

        // get balance
        BankAccount myBankAccount = bankAccountDao.selectByUserId(userId);
        int myBalance = myBankAccount.getBalance();

        // Output an exception if the balance is insufficient
        if (bookPrice >myBalance) {
            int shortage = bookPrice-myBalance;
            throw new InsufficientFundsException("Out of balance.", shortage);
        }

        // Add purchased books to the purchased books table
        BoughtBook myBoughtBook = new BoughtBook();
        myBoughtBook.setUserId(userId);
        myBoughtBook.setBookId(bookId);
        boughtBookDao.insert(myBoughtBook);

        // reduce balance and update
        int afterMyBalance = myBalance-bookPrice;
        myBankAccount.setBalance(afterMyBalance);
        bankAccountDao.update(myBankAccount);
    }

Balance shortage exception class


/**
 * Out of balance exception
 */
public class InsufficientFundsException extends Exception {

    private int shortage;

    /**
     * Exception message and shortfall
     *
     * @param message Exception message
     * @param shortage shortage
     */
    public InsufficientFundsException(String message, int shortage) {
        super(message);
        this.shortage = shortage;
    }

    public int getShortage() {
        return this.shortage;
    }
}

Reason for outputting check exception

Whether you can buy a book or not depends on your current bank account balance. In short, an exceptional situation may occur in which the book cannot be purchased, so a check exception is output and the caller is forced to change the processing to another process or issue an error message and force handling. If an unchecked exception is output, the caller may not realize that the handling needs to be implemented, and the current thread may stop without entering the recovery process.

Include additional information in handling

It may be useful for the handling side to include additional information in the output exception. For example, in the above code, by including the insufficient balance information in the exception, when the handling side wants to issue an error message to the user, it can be clearly indicated how much the balance is insufficient.

Example that requires a runtime exception

Consider the following service layer method.

  • Send the specified amount to the specified party
  • Refer to the “Bank account” table of the remittance partner
  • Add “Bank account” balance by specified amount
  • Output argument exception if the specified remittance amount is not a positive number

  • I omit the process of reducing my balance.

** Money transfer method **

    /**
     * Send the specified amount to the specified party
     * Specify a positive number for the amount to be sent
     *
     * @param transferPrice Transfer amount
     * @param targetUserId ID of remittance partner
     */
    public void transferMoney(int transferPrice, int targetUserId) {

        if (transferPrice <= 0) {
            throw new IllegalArgumentException("The amount to send must be a positive number.");
        }

        BankAccount targetBankAccount = bankAccountDao.selectByUserId(targetUserId);
        int nowBalance = targetBankAccount.getBalance();
        int afterBalance = nowBalance + transferPrice;
        targetBankAccount.setBalance(afterBalance);
        bankAccountDao.update(targetBankAccount);
    }

Reason for outputting runtime exception

This method prohibits the specification of remittance amount other than a positive number. If a negative number is specified, the balance of the other party will be reduced. Since the implementation of the method caller can prevent the specification of non-positive numbers by 100%, if a non-positive number is specified, it is judged as a program bug, a runtime exception is output, and the current thread is stopped. This is because if processing is continued, unintended processing may run, which is dangerous.

(Supplement) Not all run-time exceptions should not be caught

Not all run-time exceptions should be caught, Spring data access exceptions are implemented as run-time exceptions and may catch and rethrow another checked exception.