A small Kotlin/JVM library that normalizes Django REST Framework error responses into a simple list of field and global errors.
DRF can return validation errors in several shapes depending on whether the response came from a serializer, nested serializer, list serializer, permission check, authentication failure, or generic API exception. That flexibility is useful on the server, but it creates repetitive parsing code in Android, desktop, CLI, or other Kotlin clients.
DRF Error Handler exists to make that client code predictable:
DRFErrorResponse(
errors = listOf(
DRFError(field = "email", message = "Enter a valid email address.")
)
)- Parses field validation errors.
- Parses nested serializer errors into dot paths such as
author.email. - Parses list serializer errors into indexed paths such as
items[1].quantity. - Treats
non_field_errorsanddetailresponses as global errors. - Handles leaked Python
ErrorDetail("...", code="...")values in JSON-like payloads. - Summarizes Django/DRF HTML error pages, including debug 404/500 responses.
- Returns a fallback global error for malformed or unexpected payloads instead of throwing.
- Has no dependency on Android UI frameworks or HTTP clients.
Add the dependency from Maven Central:
repositories {
mavenCentral()
}
dependencies {
implementation("io.github.kallyas:drferrorhandler:0.1.1")
}For local development, publish it to your local Maven repository:
./gradlew publishToMavenLocalimport com.github.kallyas.DRFErrorHandler
val response = DRFErrorHandler.parse(
"""
{
"email": ["Enter a valid email address."],
"password": ["This field is required."]
}
""".trimIndent()
)
response.errors.forEach { error ->
println("${error.field}: ${error.message}")
}Output:
email: Enter a valid email address.
password: This field is required.
DRF exception responses usually use a top-level detail key:
val response = DRFErrorHandler.parse(
"""{"detail": "Authentication credentials were not provided."}"""
)
val message = response.globalErrors.firstOrNull()?.messageNested objects are flattened into field paths:
val response = DRFErrorHandler.parse(
"""
{
"author": {
"email": ["Enter a valid email address."]
}
}
""".trimIndent()
)
val emailErrors = response.getErrorsForField("author.email")List indexes are preserved:
val response = DRFErrorHandler.parse(
"""
[
{},
{"title": ["This field is required."]}
]
""".trimIndent()
)
val titleErrors = response.getErrorsForField("[1].title")val parsed = DRFErrorHandler.parse(errorBody)
emailInput.error = parsed.getErrorsForField("email").firstOrNull()?.message
passwordInput.error = parsed.getErrorsForField("password").firstOrNull()?.message
parsed.globalErrors.firstOrNull()?.let { error ->
showSnackbar(error.message)
}When Django returns an HTML debug page for a 404 or 500 response, the parser returns a concise global error instead of the full HTML document:
val response = DRFErrorHandler.parse(htmlErrorBody)
val message = response.globalErrors.firstOrNull()?.message
// Example: "OperationalError at /api/orders/"Parses a raw DRF error response string and returns a normalized response.
The method is safe to call from networking error paths. It does not throw for
malformed input; instead, it returns one global error containing the original
payload, or "An unknown error occurred." for blank input.
data class DRFError(
val field: String?,
val message: String
)field is null for global errors, non_field_errors, and generic detail
responses.
data class DRFErrorResponse(
val errors: List<DRFError>
) {
val hasErrors: Boolean
fun getErrorsForField(field: String): List<DRFError>
val globalErrors: List<DRFError>
}- Attach DRF validation messages to Android form fields.
- Display global API failures from authentication, permission, throttling, or server errors.
- Normalize nested serializer errors before rendering them in a client app.
- Send consistent error paths to logs, crash reports, or analytics.
- Reuse one parser across JVM, Android, CLI, and desktop Kotlin code.
- This library only parses and normalizes errors. It does not make HTTP requests.
- Field matching is exact. For example,
items[0].nameanditems.nameare different paths. - Error messages are returned as provided by the backend. Handle localization on the server or in the calling app.
- HTML error pages are summarized from the
<title>tag when available, or from stripped body text otherwise. ErrorDetail(...)support is a compatibility fallback for malformed payloads. Prefer valid JSON responses from your API.
Run tests:
./gradlew testPublish to the local Maven repository:
./gradlew publishToMavenLocal