포스트

언체크 예외에 대한 명시적 throws 선언시 예외 타입에 의한 컴파일 에러 발생

언체크 예외에 대한 명시적 throws 선언시 예외 타입에 의한 컴파일 에러 발생

해당 포스팅을 읽으면:

언체크(런타임) 예외는 catch로 예외를 잡지 않더라도 throws를 명시적으로 선언하지 않아도 됩니다. 즉, 예외에 대한 처리를 명시하지 않아도 됩니다.

그런데 언체크 예외가 발생하는 메서드에서 throws Exception을 명시적으로 선언했을 때, 메서드 호출부에서 이를 처리하지 않으면 컴파일 에러가 발생합니다.

위 문제 상황에 대한 인사이트를 얻을 수 있습니다.


Environments

  • OS: macOS Sequoia 15.6
  • CPU: x86
  • Java: temurin-21.0.8
  • IDE: IntelliJ Ultimate 2025

1. 서론

인프런 - 김영한의 스프링 DB 1편 - 스프링 예외 추상화 이해 강의를 학습하던 중, 예외 처리와 관련한 타 수강생의 질문글이 올라왔는데 오랜 기간 답변되지 않고 있어서 이에 대한 답변 용도로 해당 포스팅을 작성한다.

사전 지식

이 글을 읽는 독자는 다음 사실을 알고 있다고 가정하고 진행됩니다.:

  • 모든 예외는 잡거나, 던져야 한다.
  • 체크 예외는 catchthrows를 통해 예외 처리를 명시해야 된다(위반시 컴파일 에러).
  • 언체크 예외는 이를 명시하지 않아도 된다. catchthrows를 모두 명시하지 않은 경우라면 자동으로 런타임에 던져진다.
  • 언체크 예외는 런타임 예외와 동일시 한다(즉, Error는 다루지 않는다).

2. 본론

예제가 진행될 기본적인 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j  
public class MyTest2 {

    // 테스트 메서드들 ...

    // 런타임 예외를 발생시키는 기본적인 메서드.
    void throwsIllegalArgException() {  
        throw new IllegalArgumentException();  
    }  
      
    // 추후 문제를 유발하는 메서드.
    // 런타임 예외를 발생시키는 것은 동일하나, 메서드가 명시적으로 throws Exception.
    void throwsException() throws Exception {  
        throw new IllegalArgumentException();  
    }
}

분량상 이후부터는 각 세부 예제를 위한 테스트 메서드만 본문에 작성한다. 위 기본 코드의 // 테스트 메서드들 ... 부분에 위치한다고 생각하면 된다.

2.1. 런타임 예외에 대한 아무런 처리를 하지 않는 경우

basic_case()가 호출하는 메서드에서 런타임 예외가 발생한다. 호출한 지점에서 잡아서 처리하지 않으므로, 밖으로 던져진다. 잡아서 처리되지 않고 JUnit 테스트 러너까지 던져지므로 테스트 메서드가 비정상 종료된다.

1
2
3
4
5
@Test  
void basic_case() {  
    throwsIllegalArgException();  
    log.info("런타임 예외는 잡아서 처리하지 않으면 자동으로 던져지기 때문에 해당 명령은 실행되지 않음");  
}

실행 결과

1
2
3
4
5
java.lang.IllegalArgumentException
	at hello.jdbc.exception.MyTest2.throwsIllegalArgException(MyTest2.java:62)
	at hello.jdbc.exception.MyTest2.basic_case(MyTest2.java:17)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	...

2.2. 런타임 예외를 잡아서 처리하는 경우

이전 케이스와 유사하지만, 예외가 발생하는 메서드를 호출하는 지점에서 catch를 통해 명시적으로 잡아서 처리했다.

1
2
3
4
5
6
7
8
9
@Test  
void try_catch_case() {  
    try {  
        throwsIllegalArgException();  
    } catch (IllegalArgumentException e) {  
        log.info("예외 캐치: IllegalArgumentException");  
        log.info("{}", e.getClass());  
    }  
}

실행 결과

1
2
예외 캐치: IllegalArgumentException
class java.lang.IllegalArgumentException

2.3. (문제 발생) 런타임 예외를 throws Exception으로 명시해서 던지는 경우

1
2
3
4
// 호출될 메서드
void throwsException() throws Exception {  
    throw new IllegalArgumentException();  
}

이 때는 아래 예제에 대해 Unhandled exception: java.lang.Exception 컴파일 에러가 발생한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test  
void call_ex_IllegalArg_to_Exception() {  
    try {  
        throwsException();  
    } catch (IllegalArgumentException e) {  
        log.info("예외 캐치: IllegalArg");  
        log.info("{}", e.getClass());  
    }  
    // 아래 블록 주석처리하면 컴파일 에러  
//    catch (Exception e) {  
//        log.info("예외 캐치: Exception");  
//        log.info("{}", e.getClass());  
//    }  
}

컴파일 에러

2.3.1. 문제 원인: 체크 예외의 부모이기도 한 Exception

Exception은 자바 애플리케이션 레벨에서 발생할 수 있는 모든 예외의 부모이다. 즉, 체크 예외의 부모이기도 하고 언체크 예외의 부모이기도 하다.

따라서 다음과 같이 Exception이라는 큰 부모를 던지면, 체크 예외가 발생해도 문법상 문제가 없다.

1
2
3
4
void throwsException() throws Exception {  
//    throw new IllegalArgumentException();  
    throw new 체크예외();
}

그런데 이를 호출하는 메서드를 보자. 자신이 호출한 메서드에서 체크 예외가 발생할 수도 있는데, 이에 대한 어떤 처리도 명시되어 있지 않다. 체크 예외는 반드시 잡거나 던짐을 명시해서 처리해야 된다! 당연히 컴파일 에러가 발생한다!

1
2
3
4
5
6
7
8
9
@Test  
void call_ex_IllegalArg_to_Exception() /*체크 예외 던지지 않음 */{  
    try {  
        throwsException();  
    } catch (IllegalArgumentException e) {  
        log.info("예외 캐치: IllegalArg");  
        log.info("{}", e.getClass());  
    } /*체크 예외 잡지 않음*/
}

2.3.2. 문제 해결: 체크 예외를 처리하기

(당연히 실무에서는 Exception 단위로 던지거나 잡아서 처리하면 좋지 않지만) 이를 해결하려면 ‘체크 예외가 던져질 가능성’에 대해 모두 명시적으로 처리해주면 된다. 가장 쉬운 방법인 모든 체크 예외의 부모 Exception을 잡도록 명시하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test  
void call_ex_IllegalArg_to_Exception() {  
    try {  
        throwsException();  
    } catch (IllegalArgumentException e) {  
        log.info("예외 캐치: IllegalArg");  
        log.info("{}", e.getClass());  
    }  
    // 아래 블록 주석처리하면 컴파일 에러  
    catch (Exception e) {  
        log.info("예외 캐치: Exception");  
        log.info("{}", e.getClass());  
    }  
}

실행 결과

1
2
예외 캐치: IllegalArg
class java.lang.IllegalArgumentException

이제 컴파일 에러 발생하지 않음

3. 결론

throws Exception은 그 어떤 예외도 던져짐을 의미한다. 즉, 체크 예외도 던져질 수 있다. 따라서 해당 메서드를 호출하는 곳에서는 체크 예외를 잡거나, 던져서 처리하는 코드를 명시해줘야 한다.

References

no ref.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.