Error Handling

If an exception occurs while a request is handled, the server will render the exception into a structured response (JSON, XML, … depending on what the client accepts) via ErrorHandler.default(), which is an alias for ErrorHandler.structured(). To customize the serialization used, pass a SerializationBuilder to structured(...).

HTML Error Pages

To get a HTML error response instead of JSON, you can add the HTML error renderer to your app as a concern:

Kotlin
val app = Layout.create()
    .addService<MyService>("service")
    .add(ErrorHandler.html())

class MyService {

    @ResourceMethod
    fun hello(): String = "Hello World"

}

Custom Error Responses

If you would like to control the response generated for exceptions, you can implement a custom ErrorMapper and add it to your app as needed. The following example will render any exception to a text response:

Kotlin
import org.genhttp.api.content.Handler
import org.genhttp.api.content.ProviderException
import org.genhttp.api.protocol.ByteString
import org.genhttp.api.protocol.ContentType
import org.genhttp.api.protocol.Request
import org.genhttp.api.protocol.Response
import org.genhttp.api.protocol.ResponseStatus

import org.genhttp.modules.errorhandling.*
import org.genhttp.modules.io.*
import org.genhttp.modules.layouting.*
import org.genhttp.modules.webservices.*

val errorHandling = ErrorHandler.from(TextErrorMapper())

val api = Layout.create()
    .addService<MyService>("service")
    .add(errorHandling)

class TextErrorMapper : ErrorMapper<Exception> {

    override suspend fun getNotFound(request: Request, handler: Handler, acceptedFormat: ByteString?): Response? {
        // hint: return null here to render the default error page of the server
        return getResponse(request, ResponseStatus.NotFound, "The requested content was not found")
    }

    override suspend fun map(request: Request, handler: Handler, error: Exception, acceptedFormat: ByteString?): Response? {
        if (error is ProviderException) {
            return getResponse(request, error.status, error.stackTraceToString())
        }

        return getResponse(request, ResponseStatus.InternalServerError, error.stackTraceToString())
    }

    private fun getResponse(request: Request, status: ResponseStatus, message: String): Response =
        request.respond()
            .status(status)
            .content(message, ContentType.TextPlain)
            .build()

}

class MyService {

    @ResourceMethod
    fun hello(): String = "Hello World"

}

The type argument of the mapper (ErrorMapper<Exception>) decides which exceptions it handles, others are passed on to the error handler further up in the chain. As generic types are erased on the JVM, ErrorHandler.from(...) reads it from the declaration of the mapper, or takes it explicitly (ErrorHandler.from(mapper, Exception::class)). The CancellationException of a coroutine that has been cancelled is never passed to a mapper.

acceptedFormat is the format negotiated from the client’s Accept header (as also used by serialization) - use it if your mapper wants to honor content negotiation like ErrorHandler.structured() does; the example above ignores it and always renders plain text.