이번 공부 노트는 코틀린 예외처리 사용 시 유용하게 쓰이는 runCatching의 대해 적어보겠다.
JAVA에서는 보통 예외처리 시 try-catch문을 통해 예외처리를 한다. 하지만 코틀린에서는 함수형 프로그래밍을 살린(코틀린다운) runCatching을 제공한다.
# runCatching란?
실제 코틀린 공식 문서에서 runCatching 내부 구현은 아래와 같이 되어있다.
@InlineOnly
@SinceKotlin("1.3")
public inline fun <R> runCatching(block: () -> R): Result<R> {
return try {
Result.success(block())
} catch (e: Throwable) {
Result.failure(e)
}
}
자세히 보면 사실 내부에서도 try-catch를 사용하는 것을 알 수 있다.
따라서 runCatching함수는 혁신적인 새로운 방법이 아닌 기존 try-catch를 함수형 스타일로 사용할 수 있게 해주는 유틸리티 함수라고 볼 수 있다!
## try-catch / runCatching 차이점
### 1. 반환 방식의 차이
- try-catch: 직접 값 반환 또는 예외 발생
- runCatching: Result 객체로 감싸서 반환
자세히 예제를 통해 알아보자!
아래 예제는 10 / 0을 실행했을 때 ArithmeticException 발생시켜 보는 예제이다.
🔶try-catch
// try-catch: 직접 값 반환
val tryResult = try {
10 / 0 // ArithmeticException 발생
} catch (e: Exception) {
-1
}
println("try-catch 결과: $tryResult") // -1

이런 식으로 try-catch는 직접 값을 반환하는 것을 볼 수 있다.
🔷 runCatching
// runCatching: Result 객체 반환
val catchingResult = runCatching { 10 / 0 }
println("runCatching 결과: 타입 ${catchingResult::class} 값 ${catchingResult}") // Failure(ArithmeticException)
println("실제 값: ${catchingResult.getOrElse { -1 }}") // -1

반면 runCatching는 Result 객체를 반환한다.
Result 객체에 대해는 다음 글에 적어놓을 예정이다. 모르는 분은 알고 오면 좀 더 이해가 수월할 것이다.
이처럼 runCatching의 가장 큰 특징은 예외를 Result 타입으로 감싸서 안전하게 처리할 수 있다는 점이다.
### 2. 함수형 체이닝 차이
- try-catch: 중첩 구조로 복잡해짐
- runCatching: map, flatMap 등을 통한 체이닝 가능
이번도 예제를 통해 자세히 알아보자!
아래 예제는 여러 단계에서 예외가 발생할 수 있는 경우 예외처리 과정이다.
🔶try-catch
// try-catch: 중첩 지옥
fun processUserData(userIdStr: String): String {
return try {
val userId = userIdStr.toInt() // 1단계: 파싱 실패 가능
try {
val userData = fetchUserFromDB(userId) // 2단계: DB 조회 실패 가능
try {
val processedData = validateAndProcess(userData) // 3단계: 검증 실패 가능
"성공: $processedData"
} catch (e: Exception) {
"데이터 처리 실패"
}
} catch (e: Exception) {
"사용자 조회 실패"
}
} catch (e: Exception) {
"ID 파싱 실패"
}
}
이 경우 물론 단계별로 함수를 만들 수 있지만 함수를 만들어도 결국 중첩 try-catch를 사용하게 된다. 그래서 코드의 가독성이 떨어지고 복잡해진다.
🔷 runCatching
fun processUserData(userIdStr: String): String {
return runCatching { userIdStr.toInt() }
.mapCatching { fetchUserFromDB(it) } // 각 단계마다 예외 처리
.mapCatching { validateAndProcess(it) }
.map { "성공: $it" }
.getOrElse { "처리 실패: ${it.message}" }
}
이처럼 체이닝을 통해 가독성도 높이고 복잡한 코드도 줄어들었다!
## runCatching 사용법
사용법이라고 할 거는 크게 없다.
우선 runCatching 스코프 안에 예외가 발생할 수 있는 작업을 실행시켜 주고 반환된 Result형으로 원하는 결과 처리를 해주면 된다!
예제를 통해 알아보자.
이번 예제는 보통 ViewModel에서 API 호출 시 사용하는 예제이다.
// API 호출 후 결과에 따른 처리
runCatching {
// API 호출 로직
apiCall()
}.onSuccess { data ->
// 성공 시 로직
updateUI(data)
}.onFailure { error ->
// 실패 시 로직
logError(error)
}
이와 같이 Result타입의 확장함수 중 onSuccess, onFailure를 사용해서 원하는 상황에 맞게 로직을 작성해 주면 끝이다!
다음 예제는 기본적인 단일 작업 처리 방법이다.
// 예외가 발생할 수 있는 작업을 안전하게 실행
val result = runCatching {
"123".toInt() // 문자열을 정수로 변환
}
// 결과 확인
if (result.isSuccess) {
println("성공: ${result.getOrNull()}")
} else {
println("실패: ${result.exceptionOrNull()?.message}")
}
이 방법은 성공/실패에 따라 서로 다른 로직을 실행해야 할 때 유용하다.
하지만 단순히 값만 필요하다면 getOrElse { 기본값 }을 사용하는 것이 더 간편하다.
# 정리
runCatching은 기존 try-catch를 함수형 스타일로 사용할 수 있게 해주는 유틸리티다.
복잡한 예외 처리나 여러 단계의 작업에서 특히 유용하며, Result 타입을 통해 안전하고 깔끔한 코드를 작성할 수 있다.
'Kotlin 공부 노트' 카테고리의 다른 글
| [Kotlin] Result 알아보기! - 예제를 통한 사용법 (0) | 2025.09.22 |
|---|---|
| [Kotlin Flow] StateFlow vs SharedFlow 예제와 함께 차이점 알아보기! (0) | 2024.11.30 |
| [Kotlin Flow] 코틀린 Flow란? - 예제와 함께 알아보기 (2) | 2024.09.17 |
| [Kotlin-Coroutine] coroutine Mutex(상호배제) 예제를 통한 사용법 (2) | 2024.06.09 |
| [Android-Kotlin] Groovy DSL -> Kotlin DSL Migration(코틀린 DSL로 의존성 관리 마이그레이션), Kotlin DSL이란? (2) | 2023.10.29 |