Java의 try-with-resources는 AutoCloseable 리소스를 자동으로 닫아주어, 기존 try-catch-finally 방식의 복잡성과 리소스 누수 위험을 해결하는 현대적인 예외 처리 구문입니다. 이 기능을 사용하면 코드가 훨씬 간결하고 가독성이 높아지며, ‘억제된 예외’ 처리를 통해 디버깅이 용이해져 안정성이 크게 향상됩니다. Java 7 이상을 사용한다면 리소스 관리를 위해 반드시 사용해야 할 필수 기능입니다.
목차
- `try-with-resources`란 무엇인가? AutoCloseable 기반의 자동 리소스 관리
- 왜 `try-with-resources`를 사용해야 하는가?
- `try-with-resources` 사용 방법 (코드 예시)
- 핵심 동작 원리: `AutoCloseable` 인터페이스
- 결론: 더 안전하고 깨끗한 코드를 위한 필수 선택
- 자주 묻는 질문(FAQ)

이번 글에서는 Java의 try-with-resources 구문을 활용하여, 파일, 네트워크, 데이터베이스 커넥션과 같은 외부 리소스 관리를 얼마나 더 안전하고 간결하게 처리할 수 있는지, 그리고 기존 try-catch-finally 방식의 문제점을 어떻게 해결하는지 상세히 알아봅니다. 자바 개발자라면 누구나 겪어봤을 리소스 누수와 복잡한 예외 처리의 악몽에서 벗어날 방법을 명확하게 제시해 드립니다.
전통적인 예외 처리 방식에서는 try-catch-finally 블록을 사용해 사용이 끝난 리소스를 직접 해제해야 했습니다. 이 방식은 코드를 불필요하게 길고 복잡하게 만들었습니다. 특히 finally 블록 안에서 리소스의 close() 메소드를 호출할 때 또 다른 예외가 발생할 수 있어, 결국 중첩된 try-catch 구문이 필요해지는 악순환이 반복되었습니다. 만약 개발자가 실수로 close() 호출을 빠뜨리기라도 하면, 시스템은 리소스 누수(Resource Leak)라는 시한폭탄을 안게 되어 심각한 장애의 원인이 될 수 있었습니다.
하지만 Java 7에서 ‘프로젝트 코인(Project Coin)’의 일부로 도입된 try-with-resources는 이러한 모든 문제를 해결하는 현대적이고 우아한 해법입니다. 이 구문이 어떻게 코드를 혁신적으로 단축시키고, 리소스 누수를 원천적으로 차단하며, 예외 처리를 더욱 안정적으로 만드는지 지금부터 자세히 살펴보겠습니다.
`try-with-resources`란 무엇인가? AutoCloseable 기반의 자동 리소스 관리
try-with-resources는 Java 7부터 도입된 예외 처리 구문입니다. try(...) 소괄호 안에 AutoCloseable 인터페이스를 구현한 리소스 객체를 선언하기만 하면, try 블록이 정상적으로 끝나든, 예외가 발생해서 끝나든 상관없이 Java가 자동으로 리소스의 close() 메소드를 호출해주는 기능입니다.
이 구문의 핵심 동작 원리는 java.lang.AutoCloseable 또는 그것을 상속하는 java.io.Closeable 인터페이스에 있습니다. try-with-resources는 이 인터페이스들을 구현한 클래스만 사용할 수 있습니다. 우리가 코드를 작성하면, 컴파일러가 이 코드를 분석하여 finally 블록에서 리소스를 닫아주는 코드를 대신 생성해줍니다. 덕분에 개발자는 더 이상 finally 블록을 직접 작성하며 리소스 해제에 신경 쓸 필요가 없어지고, 이것이 바로 자동 리소스 관리의 핵심입니다.
왜 `try-with-resources`를 사용해야 하는가?
try-with-resources는 단순히 코드를 줄여주는 것 이상의 세 가지 명확하고 강력한 장점을 제공합니다. 기존 try-catch-finally 방식의 고질적인 문제들을 어떻게 해결하는지 비교를 통해 알아보겠습니다.
1. 압도적인 코드 가독성 및 간결성
finally 블록과 null 체크, 중첩된 예외 처리가 사라지면서 코드의 본질적인 목적이 명확하게 드러납니다. 다음은 두 방식의 차이를 보여주는 대표적인 예시입니다.
| 구분 | Before: try-catch-finally |
After: try-with-resources |
|---|---|---|
| 코드 |
|
|
| 분석 | 리소스 선언, null 체크, close() 호출, close() 예외 처리 등 보일러플레이트 코드가 많아 핵심 로직을 파악하기 어렵습니다. |
리소스 선언과 사용, 예외 처리가 명확하게 분리되어 코드의 의도가 한눈에 들어옵니다. 코드가 매우 간결하고 깔끔합니다. |
2. 완벽한 리소스 누수 방지
try 블록이 어떤 시나리오로 종료되더라도 리소스의 close() 호출을 100% 보장합니다. 정상적으로 블록이 끝나거나, 중간에 return 또는 break를 만나거나, 예상치 못한 예외가 발생하는 등 모든 경우의 수를 포함합니다. 이는 개발자의 사소한 실수가 시스템 전체에 영향을 미치는 리소스 누수 문제를 원천적으로 차단하여 리소스 관리의 안정성을 극대화합니다. 성능 저하에 대한 우려도 없습니다. try-with-resources는 컴파일 시점에 try-finally와 거의 동일한 바이트코드를 생성하므로, 성능상 차이는 사실상 없다고 봐도 무방합니다.

3. 안정적인 예외 처리 (억제된 예외, Suppressed Exceptions)
전통적인 try-finally 방식에는 치명적인 약점이 있습니다. 만약 작업을 처리하는 try 블록과 리소스를 닫는 finally 블록 양쪽에서 모두 예외가 발생하면, finally에서 발생한 두 번째 예외가 try 블록의 원본 예외를 덮어쓰게 됩니다. 이 경우, 문제의 진짜 원인이었던 첫 번째 예외 정보가 사라져 디버깅이 매우 어려워집니다.
try-with-resources는 ‘억제된 예외’라는 개념으로 이 문제를 해결합니다. try 블록의 예외를 원본(Primary) 예외로 유지하고, close() 과정에서 발생한 예외는 ‘억제된(Suppressed)’ 예외로 원본 예외에 추가합니다. 따라서 개발자는 어떤 예외 정보도 놓치지 않고 확인할 수 있습니다.
Throwable.getSuppressed() 메소드를 사용하면 억제된 예외에 접근할 수 있습니다.
catch (Exception e) {
System.out.println("원본 예외: " + e.getMessage());
for (Throwable suppressed : e.getSuppressed()) {
System.out.println("억제된 예외: " + suppressed.getMessage());
}
}
`try-with-resources` 사용 방법 (코드 예시)
try-with-resources의 문법은 매우 직관적이며 다양한 상황에 유연하게 적용할 수 있습니다.
1. 단일 리소스 사용 (기본 문법)
가장 일반적인 형태로, 하나의 리소스를 try 소괄호 안에 선언하고 사용하는 방식입니다. 파일 스트림, 데이터베이스 커넥션, 소켓 등 대부분의 자원 처리에 활용됩니다.
// 파일 읽기 예시
try (BufferedReader br = new BufferedReader(new FileReader("input.txt"))) {
System.out.println(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
2. 다중 리소스 사용
세미콜론(;)을 사용하여 두 개 이상의 리소스를 한 번에 선언하고 관리할 수 있습니다. 이때 매우 중요한 점은 리소스가 닫힐 때 선언된 순서의 역순으로 닫힌다는 것입니다. 이는 리소스 간의 의존성이 있을 때 안전한 해제를 보장합니다.
// input.txt 파일을 읽어 output.txt 파일로 복사하는 예시
try (FileInputStream in = new FileInputStream("input.txt");
FileOutputStream out = new FileOutputStream("output.txt")) {
// 리소스 사용 로직...
} catch (IOException e) {
e.printStackTrace();
}
// 이 코드에서는 out.close()가 먼저 호출되고, 그 다음 in.close()가 호출됩니다.
3. Java 9부터 개선된 사용법
Java 9 이전에는 리소스 객체를 반드시 try 소괄호 안에서 생성하고 초기화해야 했습니다. 하지만 Java 9부터는 final이거나 ‘사실상 final(Effectively Final)’인 변수라면 외부에서 선언된 리소스도 try 블록에서 참조할 수 있어 코드가 더욱 유연해졌습니다. ‘사실상 final’이란, final 키워드가 붙지 않았지만 초기화된 이후 값이 한 번도 재할당되지 않은 변수를 의미합니다.
// Java 9+ 버전
// 외부에서 선언된 final 변수를 try-with-resources에서 사용
final BufferedReader br = new BufferedReader(new FileReader("input.txt"));
try (br) {
System.out.println(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
핵심 동작 원리: `AutoCloseable` 인터페이스
try-with-resources의 모든 마법을 가능하게 하는 열쇠는 바로 AutoCloseable 인터페이스입니다. 이 인터페이스는 다음과 같이 단 하나의 추상 메소드만을 가지고 있습니다.
public interface AutoCloseable {
void close() throws Exception;
}
try-with-resources의 try 소괄호 안에 선언된 객체는 반드시 이 인터페이스의 구현체여야 합니다. try 블록이 종료될 때, Java는 내부적으로 이 객체의 close() 메소드를 자동으로 호출해줍니다. Java의 표준 라이브러리에 있는 대부분의 입출력 스트림, 소켓, JDBC 관련 클래스들은 이미 AutoCloseable을 구현하고 있어 즉시 try-with-resources와 함께 사용할 수 있습니다.
더 나아가, 우리가 직접 만드는 클래스에도 이 자동 리소스 관리 기능을 적용할 수 있습니다. AutoCloseable을 구현하기만 하면 됩니다.
// 직접 만든 리소스 관리 클래스
class MyResource implements AutoCloseable {
public MyResource() {
System.out.println("MyResource 생성됨");
}
public void doWork() {
System.out.println("MyResource 작업 수행");
}
@Override
public void close() throws Exception {
// 리소스 해제 로직을 여기에 구현
System.out.println("MyResource 해제됨!");
}
}
// 사용 예시
public class Main {
public static void main(String[] args) {
try (MyResource myResource = new MyResource()) {
myResource.doWork();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 출력 결과:
// MyResource 생성됨
// MyResource 작업 수행
// MyResource 해제됨!

결론: 더 안전하고 깨끗한 코드를 위한 필수 선택
try-with-resources는 기존 try-catch-finally의 복잡성, 리소스 누수 가능성, 예외 덮어쓰기 문제를 모두 해결한 Java의 표준 리소스 관리 방식입니다. 코드의 안정성과 유지보수성을 극적으로 향상시키는 이 강력한 기능을 사용하지 않을 이유가 없습니다.
만약 여러분의 프로젝트가 Java 7 이상을 사용한다면, InputStream, OutputStream, java.sql.Connection, java.sql.Statement 등 AutoCloseable을 구현하는 모든 리소스는 반드시 Java try-with-resources 구문으로 다루는 것이 최선의 선택입니다. 기존 코드에 남아있는 복잡한 try-catch-finally 블록이 있다면, 이번 기회에 리팩토링하는 것을 적극 권장합니다.
지금 바로 여러분의 코드에서 finally 블록을 찾아 try-with-resources로 개선해보세요. 더 깨끗하고 안전한 코드를 경험할 수 있습니다.
자주 묻는 질문(FAQ)
Q. `try-with-resources` 구문은 언제부터 사용할 수 있었나요?
A. `try-with-resources`는 Java 7 버전부터 도입되었습니다. 따라서 Java 7 이상의 환경이라면 어디서든 사용할 수 있습니다.
Q. `AutoCloseable` 인터페이스를 구현하지 않은 클래스도 `try-with-resources`에서 사용할 수 있나요?
A. 아니요, 사용할 수 없습니다. `try-with-resources` 구문은 `try()` 소괄호 안에 선언된 객체가 반드시 `java.lang.AutoCloseable` 또는 `java.io.Closeable` 인터페이스를 구현한 경우에만 동작합니다. 그렇지 않으면 컴파일 오류가 발생합니다.
Q. `try-with-resources`를 사용하면 성능이 저하될 수 있나요?
A. 아니요, 성능 저하에 대한 걱정은 하지 않으셔도 됩니다. `try-with-resources` 구문은 컴파일 시점에 기존의 `try-catch-finally` 구문과 거의 동일한 바이트코드로 변환됩니다. 따라서 런타임 성능에 미치는 영향은 사실상 없다고 볼 수 있습니다.