컴파일러가 예외 누락을 체크해주기 때문에, Exception을 잡아서 try-catch로 처리하거나 혹은 throws를 선언해야한다.
말 그대로 Runtime(실행 중)단계에 발생할 수 있는 에외로, Exception을 잡아서 처리하지 않아도 throws를 생략할 수 있다.
Unchecked Exception(RuntimeException)을 사용한다.Checked Exception을 사용한다.Checked Excepton은 컴파일러가 예외 누락을 체크해주기 때문에, 개발자가 실수로 예외를 놓치는 것을 방지한다. 따라서 try-catch 혹은 throws를 명시적으로 작성해주어야 한다.
하지만 이러한 방식이 좋아보일지는 몰라도, 치명적인 문제점이 발생한다.

위 그림은 Repository와 NetworkClient 계층에서 Checked Exception이 발생했을 때의 도식도이다. (SQLException, ConnectException은 Checked Exception이다.)
Respository와 NetworkClient에서 throws한 SQLException, ConnectException은 Service, Controller 계층에서도 모두 throws를 선언해야 한다.
Spring MVC에서는 Controller에서 throws한 것을 ControllerAdvice에서 예외를 공통으로 처리한다.
SQLException, ConeectException과 같이 복구가 불가능한 예외는 별도의 오류 로그를 남겨야 하며, 개발자가 오류를 인지할 수 있도록 최대한 빨리 알려야 한다.
예를 들어, SQLException이 잘못된 SQL을 작성해서 발생했다면, 해당 SQL을 수정해서 다시 배포하기 전까지 복구가 불가능하며, 사용자는 같은 문제를 겪게 된다.
class Repository {
    public void call() throws SQLException {
        throw new SQLException("ex");
    }
}
class NetworkClient {
    public void call() throws ConnectException {
        throw new ConnectException("연결 실패");
    }
}
class Service {
    Repository repository = new Repository();
    NetworkClient networkClient = new NetworkClient();
    public void logic() throws SQLException, ConnectException {
        repository.call();
        networkClient.call();
    }
}
서비스는 이 둘을 모두 호출하게 된다.
하지만, 서비스에서 처리가 불가능하기 때문에 throws한다.
class Controller {
    Service service = new Service();
    public void request() throws SQLException, ConnectException {
        service.logic();
    }
}
결국 SQLException과 ConnectException은 어디에서도 처리가 불가능하며, 2가지 문제가 발생한다.
SQLException, ConnectException을 의존하게 된다.
`RuntimeException`을 상속하는 `RuntimeSQLException, RuntimeConnectException`를 대신 발생하게 하면 Checked Exception의 문제를 해결할 수 있다.
코드로 알아보자.
class RuntimeSQLException extends RuntimeException {
    public RuntimeSQLException(Throwable cause) {
        super(cause);
    }
}
이때 생성자를 Throwable를 받게 되면 기존에 발생한 예외를 넣을 수 있다.
class RuntimeConnectException extends RuntimeException {
    public RuntimeConnectException(String message) {
        super(message);
    }
}
class Repository {
    public void call() {
        try {
            runSQL();
        } catch (SQLException e) {
            throw new RuntimeSQLException(e);
        }
    }
    private void runSQL() throws SQLException {
        throw new SQLException("ex");
    }
}
Repository에서 SQLException이 발생하도록 하여 잡아서 RuntimeSQLException을 호출하도록 하여 체크 예외를 처리한다.
위 코드와 같이 기존의 예외를 포함하여 처리해야 스택 트레이스에서 확인이 가능하다.
class NetworkClient {
    public void call() {
        throw new RuntimeConnectException("연결 실패");
    }
}
class Service {
    Repository repository = new Repository();
    NetworkClient networkClient = new NetworkClient();
    public void logic() {
        repository.call();
        networkClient.call();
    }
}
Unchecked Exception이기 때문에 throws를 선언하지 않아도 된다.
class Controller {
    Service service = new Service();
    public void request() {
        service.logic();
    }
}
마찬가지로 Unchecked Exception이기 때문에 throws를 선언하지 않아도 된다.
@Slf4j
public class UncheckedAppTest {
    @Test
    void unchecked() {
        Controller controller = new Controller();
        assertThatThrownBy(() -> controller.request())
                .isInstanceOf(Exception.class);
    }
    @Test
    void printEx() {
        Controller controller = new Controller();
        try {
            controller.request();
        } catch (Exception e) {
            // e.printStackTrace();
            log.info("ex", e);
        }
    }
}
throws를 선언하지 않아도 정상적으로 예외가 발생하는 것을 검증하는 코드로 정상 종료된다.
Unchecked Exception을 사용함으로,
Checked Exception처럼 예외를 강제로 의존하지 않아도 된다.