A multi-module error handling library for Android that brings structure to API error management. Type-safe failure hierarchies, a Result monad, and pluggable exception mapping — all with clean separation between infrastructure and domain errors.
| Module | Artifact | Description |
|---|---|---|
| core | errorz:core |
Failure types, Result monad, exception mapping, safeCall |
| retrofit-adapter | errorz:retrofit-adapter |
Retrofit bridge — safeApiCall, response wrapping |
| serialization-kotlinx | errorz:serialization-kotlinx |
Kotlinx Serialization error body parser |
Add the JitPack repository to your settings.gradle.kts:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}Add dependencies to your module's build.gradle.kts:
dependencies {
// Core (required)
implementation("com.github.alicankorkmaz-sudo.errorz:core:v0.1.1")
// Retrofit adapter (if using Retrofit)
implementation("com.github.alicankorkmaz-sudo.errorz:retrofit-adapter:v0.1.1")
// Kotlinx Serialization support (if using kotlinx.serialization)
implementation("com.github.alicankorkmaz-sudo.errorz:serialization-kotlinx:v0.1.1")
}Initialize ExceptionMapperConfig in your Application.onCreate():
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
ExceptionMapperConfig.configure(
mappers = listOf(
NetworkExceptionMapper(), // IOException → NoConnection, SocketTimeout → Timeout
HttpExceptionMapper(), // HttpException → ServerError / Unauthorized
KotlinxSerializationExceptionMapper(), // SerializationException → ParseError
),
errorBodyParser = KotlinxSerializationErrorBodyParser(),
)
}
}class UserProfileRepositoryImpl(
private val service: UserProfileService,
) : UserProfileRepository {
override suspend fun getProfile(userId: String): Result<UserProfile> {
return safeApiCall { service.getProfile(userId) }
.map { it.toDomain() }
.mapError { it.toDomainFailure() }
}
}viewModelScope.launch {
repository.getProfile(userId)
.onSuccess { profile ->
_state.value = ProfileState.Loaded(profile)
}
.onError { failure ->
_state.value = when (failure) {
is UserFailure.NotFound -> ProfileState.NotFound
is InfrastructureFailure.NoConnection -> ProfileState.Offline
else -> ProfileState.Error(failure.toString())
}
}
}Failure (sealed interface)
├── Failure.Domain — Business logic errors (your sealed interfaces)
├── Failure.Infrastructure — Technical errors
│ ├── NoConnection
│ ├── Timeout
│ ├── ServerError(code, apiError?)
│ ├── Unauthorized(apiError?)
│ └── ParseError(cause?)
└── UnknownFailure(throwable) — Catch-all
Define domain-specific failures by implementing Failure.Domain:
sealed interface UserFailure : Failure.Domain {
data object NotFound : UserFailure
data object Suspended : UserFailure
data class ValidationFailed(val errors: List<String>) : UserFailure
}Map infrastructure errors to domain errors at the repository level:
private fun Failure.toDomainFailure(): Failure = when (this) {
is InfrastructureFailure.ServerError -> when (code) {
404 -> UserFailure.NotFound
403 -> UserFailure.Suspended
422 -> UserFailure.ValidationFailed(apiError?.errors.orEmpty())
else -> this
}
else -> this
}// Transform success data
result.map { dto -> dto.toDomain() }
// Chain operations that return Result
result.flatMap { id -> repository.findById(id) }
// Transform failures
result.mapError { failure -> failure.toDomainFailure() }
// Side effects
result
.onSuccess { data -> log("Got: $data") }
.onError { failure -> log("Failed: $failure") }Implement ExceptionMapper to handle your own exception types:
class FirebaseExceptionMapper : ExceptionMapper {
override fun map(throwable: Throwable): Failure? = when (throwable) {
is FirebaseAuthException -> InfrastructureFailure.Unauthorized()
is FirebaseNetworkException -> InfrastructureFailure.NoConnection
else -> null // return null to pass to next mapper
}
}Register it in ExceptionMapperConfig.configure().
Use safeCall for non-Retrofit operations (database, file I/O, etc.):
suspend fun readFromCache(key: String): Result<String> {
return safeCall { cache.get(key) }
}:core
failure/ Failure, InfrastructureFailure, UnknownFailure
result/ Result, map, flatMap, mapError, onSuccess, onError
api/ ApiError, RawApiError, ApiResponseContract, ErrorBodyParser
mapper/ ExceptionMapper, CompositeExceptionMapper, NetworkExceptionMapper, ExceptionMapperConfig
SafeCall.kt safeCall(), handleApiResponse()
:retrofit-adapter
adapter/ RetrofitApiResponse
mapper/ HttpExceptionMapper
SafeApiCall.kt safeApiCall(), safeApiCallNullable()
:serialization-kotlinx
parser/ ApiErrorDto, KotlinxSerializationErrorBodyParser
mapper/ KotlinxSerializationExceptionMapper
- Android minSdk 26
- Kotlin 2.1+
- Java 11
Copyright 2025 alicankorkmaz-sudo
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0