Handling errors properly when using Retrofit in Kotlin is crucial for delivering a reliable and user-friendly Android application. Whether you’re dealing with network failures, parsing errors, or API-specific error responses, following a structured error-handling pattern helps ensure your app remains robust and user-friendly.

Retrofit is one of the most widely used HTTP clients for Android and provides a simple way to make network requests. However, beginners often overlook comprehensive error handling, which leads to unexpected crashes or poor user experience.
Below are best practices for error handling in Kotlin using Retrofit, including complete sample code divided by class and interface.
1. Use a Sealed Class for API Result
// ApiResult.kt
sealed class ApiResult<out T> {
data class Success<out T>(val data: T): ApiResult<T>()
data class Error(val exception: Throwable): ApiResult<Nothing>()
}
2. Retrofit Service Interface
// ApiService.kt
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: Int): Response<User>
}
3. API Call Wrapper in RepositoryAPI Call Wrapper in Repository
// UserRepository.kt
class UserRepository(private val apiService: ApiService) {
suspend fun getUserById(id: Int): ApiResult<User> {
return try {
val response = apiService.getUser(id)
if (response.isSuccessful) {
response.body()?.let {
ApiResult.Success(it)
} ?: ApiResult.Error(Exception("Empty body"))
} else {
ApiResult.Error(Exception("HTTP ${response.code()} ${response.message()}"))
}
} catch (e: IOException) {
ApiResult.Error(Exception("Network Error: ${e.localizedMessage}"))
} catch (e: Exception) {
ApiResult.Error(e)
}
}
}
4. ViewModel Usage
// UserViewModel.kt
class UserViewModel(private val repository: UserRepository): ViewModel() {
private val _userLiveData = MutableLiveData<ApiResult<User>>()
val userLiveData: LiveData<ApiResult<User>> = _userLiveData
fun fetchUser(id: Int) {
viewModelScope.launch {
val result = repository.getUserById(id)
_userLiveData.postValue(result)
}
}
}
5. UI Layer Observation
// In Activity or Fragment
viewModel.userLiveData.observe(viewLifecycleOwner) { result ->
when(result) {
is ApiResult.Success -> {
// Show user data
}
is ApiResult.Error -> {
// Show error message
Toast.makeText(context, result.exception.message, Toast.LENGTH_SHORT).show()
}
}
}
Best Practice Summary
- Always catch
IOExceptionand generalException. - Use
Response.isSuccessfulto check HTTP success. - Return a consistent result with a sealed class.
- Show meaningful errors to users with fallback messages.
For a more advanced approach, consider implementing Retrofit’s custom call adapter.
By applying these patterns, you improve app resilience, make testing easier, and provide a smoother user experience.