Skip to content

About

General-purpose status codes. Like HTTP status codes, but not tied to a protocol. A three-tier taxonomy for any layer of your app, with RFC 9457 compatibility.

Topics

Resources

Contributing

Stars

3 stars

Watchers

1 watching

Forks

Repository files navigation

A Kotlin library to classify and handle success and failure.

A small, dependency-free status and error taxonomy for application outcomes, with extensible codes, protocol mappings, validation, typed exceptions, and optional Result<T, E> integration.

Maven Central Build License Kotlin

Part of Kiit Β· Docs

Kiit Codes overview

πŸ“š Table of Contents

# Topic Description
1 πŸ’‘ Why The recurring problems Codes is designed to solve, and how it solves them
2 πŸš€ Start Installation, a quick example, and what built-in codes look like
3 πŸ—‚οΈ Taxonomy The fixed Status β†’ Group β†’ Code model that everything else builds on
4 🧩 Extensibility Adding custom, domain-specific codes on top of the built-in taxonomy
5 πŸ”€ Protocols Mapping application outcomes to HTTP, gRPC, and other transports
6 βš™οΈ Usage Status handling, validation, exceptions, and Result covered in the docs
7 πŸ—ΊοΈ Roadmap Project history, current focus, and planned improvements
8 πŸ“– Learn More Deeper documentation, design rationale, and FAQ on kiit.dev
9 πŸ“‹ Requirements Supported platforms and runtime dependencies
10 🀝 Contributing How to build, test, and contribute changes
11 πŸ“„ License Apache 2.0 license terms

Why

Applications need to communicate a simple idea consistently: what happened?

In practice, success and failure are often modeled differently across domains, application layers, protocols, and error-handling approaches. This creates several recurring problems:

# Problem Description
1 Classification No shared taxonomy for modeling success and failure.
2 Consistency Outcomes vary across application layers and protocols.
3 Fragmentation Validation, exceptions, statuses, and results use different approaches.
4 Boilerplate Similar error types and handling are rebuilt across projects and teams.
5 Specificity Generic errors often lack precise domain meaning.

kiit-codes provides a shared application-level model for these concerns.

A fixed taxonomy provides consistent classification, extensible codes preserve domain-specific meaning, and protocol mappings keep application outcomes independent from their transport. The same model can then be used across statuses, validation, exceptions, and result types.

Start

Gradle (Kotlin DSL):

dependencies {
    implementation("dev.kiit:kiit-codes:1.1.0")
}

A status can represent an outcome without requiring exceptions or a result type:

import kiit.codes.*

fun authorize(userId: String, requesterId: String): Status =
    if (userId != requesterId) Restricted.UNAUTHORIZED
    else Succeeded.SUCCESS

when (val status = authorize(userId, requesterId)) {
    is Passed -> log.info("ok: ${status.name}")
    is Failed -> log.warn("failed: ${status.name} β€” ${status.title}")
}

Built-in codes expose stable fields suitable for application logic, logging, APIs, and diagnostics:

{
    "name"    : "CONFLICT",
    "group"   : "Rejected",
    "origin"  : "kiit.dev",
    "scope"   : "codes",
    "success" : false,
    "title"   : "The request conflicts with the current state"
}

Taxonomy

Codes uses a three-tier model:

Tier Name Fixed/Open Suggested wording
Tier 1 Status Fixed Classifies an outcome as Passed or Failed.
Tier 2 Group Fixed Classifies the kind of success or failure.
Tier 3 Code Open Identifies a specific outcome using built-in or domain-specific codes.

Status β†’ Group β†’ Code

Status and Group are fixed so applications share the same high-level meaning. Code is open: Kiit supplies common defaults while applications can add their own domain-specific codes.

Kiit Codes taxonomy

The two statuses are:

  • Passed β€” Succeeded, Pending, Excluded, Information
  • Failed β€” Restricted, Invalid, Rejected, Unserved

Each code provides a name, group, origin, title, and success flag. Built-in codes use the kiit.dev origin and each group has a default code for cases where more precision is unnecessary.

The built-in taxonomy contains common application outcomes such as SUCCESS, CREATED, DENIED, INVALID_VALUE, CONFLICT, TIMEOUT, and UNEXPECTED.

Extensibility

The taxonomy stays consistent while codes remain open to your domain.

Kiit Codes custom codes

Custom codes use the same group types as Kiit's defaults:

import kiit.codes.Failed

// Example custom code
val PAYMENT_DECLINED = Failed.Rejected(
    name = "PAYMENT_DECLINED",
    title = "Payment declined",
    origin = "payments"
)

PAYMENT_DECLINED remains a Rejected outcome everywhere in the system while retaining its domain-specific identity. The origin keeps custom namespaces distinct from Kiit and from other modules or teams. Use a real domain when you have one (stripe.com), or a specific name otherwise. kiit-codes can't detect two teams choosing the same name.

Protocols

Application outcomes should describe what happened, independently of how they are transported.

Kiit codes can be mapped to protocol-specific representations at system boundaries.

Kiit Codes protocol mappings

HTTP

CodesToHttp maps statuses to HTTP codes using group defaults plus specific overrides where needed.

import kiit.codes.*

val http = CodesToHttp()

http.toCode(Succeeded.CREATED) // 201
http.toCode(Invalid.INVALID_VALUE) // 400
http.toCode(Invalid.NOT_FOUND) // 404

There is no reverse conversion from an HTTP code to a status. Many statuses share one code, so a code can't identify a single status.

gRPC

CodesToGrpc follows the same model and covers all gRPC status codes.

import kiit.codes.*

val grpc = CodesToGrpc()

grpc.toCode(Restricted.DENIED)   // 7, PERMISSION_DENIED
grpc.toCode(Rejected.CONFLICT)   // 6, ALREADY_EXISTS

RFC 9457 (Problem Details)

ProblemConverter converts a status into an RFC 9457 problem details object, for an HTTP API response. A kiit-native equivalent, CodeDetail, covers boundaries an HTTP-shaped response doesn't fit β€” service-to-service calls, background jobs, and similar.

The type is {baseUrl}/{scope...}/{status}/{group}/{name}, lowercase with dashes. status is passed or failed, and each . in a scope starts a new segment, so payments.cards becomes payments/cards. With no entry in baseUrls:

  1. Domain origin: a status with the origin stripe.com gets https://stripe.com/docs/codes/..., with no registration.
  2. Any other origin: an id such as myapp1 is not a host, so the type is the relative /docs/codes/.... RFC 9457 prefers absolute URIs, and a relative type resolves against the response's own host, so register a base URL to get an absolute one.

Add an entry when an origin's docs live elsewhere. An entry always wins and is always absolute.

import kiit.codes.formats.*

val problems = ProblemConverter(baseUrls = mapOf("payments" to "https://example.com/problems"))

problems.convert(PAYMENT_DECLINED)
{
    "type": "https://example.com/problems/failed/rejected/payment-declined",
    "title": "Payment declined",
    "status": 409,
    "code": "payments::Failed:Rejected:PAYMENT_DECLINED"
}

type is the problem's identity as well as its docs pointer, so pick the base once and keep it, and do not let two statuses produce the same type. code is the exact {origin}:{scope}:{Status.code}, the same string as CodeDetail.code. Read it instead of parsing type, which is lowercase and may not carry the origin. Every error in the Err goes into errors: a list is expanded, and a single Err.on("email", "Missing") is one entry that keeps its field.

Problem.type CodeDetail.code
Identity yes, RFC 9457's primary identifier yes, exact
Docs yes, when it resolves no
Self-contained no, needs a host or a base yes
Case lowercase-dash as written

To set your own type, no new API is needed. Pass a typeBuilder to convert, call convertWithUrl with a base and an empty suffix, or replace the field with copy(type = ...). code is built from the status, so it does not change.

The mapping abstraction is not limited to HTTP and gRPC. CodeLookup and CompositeLookup can be used to define or extend mappings for other protocols.

val lookup = CompositeLookup(
    base = CodesToHttp(),
    extensions = mapOf(PAYMENT_DECLINED to 402)
)

lookup.toCode(PAYMENT_DECLINED) // 402

Usage

Codes is designed to work at different application boundaries without requiring one error-handling style.

Status

Use Status when the outcome itself is enough.

import kiit.codes.*

fun authorize(userId: String, requesterId: String): Status =
    if (userId != requesterId) Restricted.UNAUTHORIZED
    else Succeeded.SUCCESS

when (val status = authorize(userId, requesterId)) {
    is Passed -> log.info("ok: ${status.name}")
    is Failed -> log.warn("failed: ${status.name} β€” ${status.title}")
}

Kiit Codes usage

Roadmap

kiit-codes has been extracted from the Kiit toolkit and polished as a standalone module. This has been used in production for over 4+ years to power mobile and server kotlin applications. Current work is focused on the Kotlin release, documentation, examples, and ecosystem integration.

# Topic Description
1 Documentation Update documentation and examples as needed.
2 Taxonomy Continue taxonomy review based on real-world usage and community feedback.

See GitHub Issues for current work and discussions.

Learn More

# Topic Description
1 Taxonomy Explore every built-in group and code, their intended meanings, defaults, and distinctions. Read the taxonomy docs.
2 Extensibility Learn how extensibility works and how to create domain-specific codes while keeping the shared taxonomy intact. Read the extensibility docs.
3 Protocols See the complete HTTP and gRPC mappings and learn how to create custom mappings. Read the protocol docs.
4 Validation Learn how Err, Checked, and collect model validation and accumulate multiple errors. Read the validation docs.
5 Exceptions See the typed exception hierarchy and patterns for integrating Codes with exception-based boundaries. Read the exception docs.
6 Result Learn how the separate kiit-result module builds Result<T, E> handling on top of the same Codes taxonomy. Read the kiit-result README.
7 FAQ Answers to common questions about the taxonomy, design choices, alternatives, adoption, AI considerations, and project maturity. Read the FAQ.
8 Design Read more about the reasoning behind fixed groups, extensible codes, protocol independence, and where Codes fits relative to domain errors. Read the design docs.

Requirements

  • Kotlin Multiplatform
  • JVM, Android, iOS (simulator, iosArm64, x64)
  • No external runtime dependencies

A native TypeScript port, @kiitdev/codes, is maintained separately in ports/kiit-codes-ts β€” checked against the Kotlin taxonomy, not generated from it. See its README for installation and usage.

Contributing

Contributions and design feedback are welcome. See BUILD.md for build, test, and publish instructions.

License

Apache License 2.0


kiit-codes is one module of Kiit β€” a lightweight, modular Kotlin toolkit for building server applications, APIs, CLIs, and jobs.

Adopt one module at a time.

About

General-purpose status codes. Like HTTP status codes, but not tied to a protocol. A three-tier taxonomy for any layer of your app, with RFC 9457 compatibility.

Topics

Resources

Contributing

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages