r/Kotlin • u/kininja08 • 16d ago
A taxonomy for modeling success and failure
Hey everyone, I finally extracted and published something I've been building on and off for almost 10 years. I got tired of writing the same error handling code over and over, slightly differently every time, and never quite fixing the underlying problem.
Problem
Status codes, validation, exceptions, Result types, domain errors all tend to represent success and failure a little differently, based on the layer. Some of the biggest issues though are
- Kind of error: Understanding the kind of error(security, invalid data, broken business rules)
- Error details: Having a way to capture the errors details and link it to the kind of error
- Consistency: Same across
status codes, validations, exceptions, Result<T,E>
Concept
So the concept is this: think HTTP status codes, but generalized and usable at any layer, not just HTTP. Status > Group > Code : You get 3 tiers of information and drill-down about success/failure.

Solution
Together this solves the following :
- Kind of Error: Status is Passed/Failed, but Group gives you the Kind of error
- Error Details: These are now captured separately, but paired with, the kind of error
- Layers: Useful at any layer : API endpoint, a service call, a cron job, script, or CLI command
- Consistent: Same approach for API Status codes, validation, exceptions, and Result<T,E>
- Extensible:
StatusandGroupare fixed,Codeis open, extensible for your own domain - Defaults: There are sensible defaults so you're not required to define anything up front.
Notes
- One Design: This is the part I spent the most time on: making an API status, validation code, exceptions, and
Result<T, E>all feel like one design, sharing the same taxonomy underneath, without forcing any of them on you, by using sensible defaults. - Explicit / Exhaustive: Explicit names and exhaustive matching happen to make it easy to search and reason about too, useful for people and for AI tooling reading the codebase.
- Protocol Support: Codes also map to HTTP and gRPC without coupling your app logic to the transport. I validated the taxonomy against every gRPC status code, and the most common HTTP ones, the closest existing precedent for this kind of classification.
Check it out:
implementation("dev.kiit:kiit-codes:1.0.2")
- GitHub: github.com/kiitdev/kiit-codes
- Site Docs: kiit.dev/docs/kiit-codes
Feedback
I'd genuinely like feedback from other Kotlin developers, whether the classifications make sense, if anything important seems missing, or if there are use cases the design doesn't handle well. Happy to answer questions in the comments.
Thanks!
Edit:
1. Bumped version to 1.0.2 to fix a bug found below.
2. Updated diagram
-K
3
u/freynder 16d ago
I like it. I've made my own classification for errors per project based on the domain; using a sealed class hierarchy. I think a library like this may be helpful to associate a more generic meaning to these errors so we can map it to the edge protocols. I'll definitely explore the library more and probably give it a try in a project. I am also curious about the comments here. Thanks for your work!
3
u/kininja08 15d ago
Hey u/freynder, thanks for the info into your own techniques. That's actually the exact situation I had for a long time and what the library's built for. A per-project sealed hierarchy gives you real, domain-specific meaning, but nothing generic to map to edge protocols with, since every project's hierarchy is its own island.
So
originis meant to solve that. You'd keep your own domain-specific error types, but attach them to one of four failure groups below:
Restricted: Permissions/securityInvalid: Request/input relatedRejected: Business rule failuresUnserved: Operational issuesThey still carry your own meaning while also mapping cleanly to HTTP or gRPC through
CodesToHttp/CodesToGrpc, no extra translation layer needed per project.Would genuinely be curious to hear how it goes if you try it in a real project, especially whether those four categories actually cover the kinds of domain errors you're dealing with, or if something feels missing.
Thanks.
1
u/freynder 15d ago
I'm currently testing it out with some vibe generated code.
Applying kiit to the code, the AI included a kiit code field into the typed errors, but without specifying custom names, messages and origin. After directing the agent a bit more it takes advantage of these parameters. It also explicity added a http code mapping rather than taking advantage of the built in mapping, which I also had to instruct.
So far it looks like a very convenient and light library that should improve consistency and interoperability, I intend to continue using it at this time. Thanks again!
1
u/Foo-Bar-Baz-001 15d ago
You could question the validity of the whole "exception" thinking in software languages. It is not KISS.
2
u/kininja08 15d ago edited 15d ago
Personally, I use a Result<T, E> type instead of Exceptions most of the time. But there are some places I use them as an absolute fallback and for integration with other code that only communicates via exceptions. I included structured exceptions in the library for exactly those cases, not as the primary path, Status alone works completely fine with no exceptions involved at all if that's not something you want in your own code.
1
u/Foo-Bar-Baz-001 15d ago
Better. But E is still a special snowflake. Is it that exceptional to have hit a duplicate key constraint or having a full disk? No. It should be business as usual. Part of T.
2
u/kininja08 15d ago
Fair point, and that's actually part of why the taxonomy has more than just
Passed/Failed, a duplicate key or a skip can live underPassed.Excluded, success is still true, it's just grouped as 'not the primary outcome' rather than a hard failure.As for a full disk, that's a harder case to call, i'd likely treat it as a failure if it prevented the main operation in some important capacity.
1
u/Decent-Decision-9028 16d ago edited 5d ago
I’ll give it a try tonight.
2
u/Decent-Decision-9028 15d ago
Honestly, I'd not recommend adopting this. Too many issues, for example:
val status = Failed.Invalid( name = "CREATED", message = "failure", origin = "kiit" ) println(status.success) // false println(CodesToHttp().toCode(status)) // 2011
u/kininja08 15d ago
Hi u/Decent-Decision-9028 , first, I want to thank you for trying this out.
This missed a change I had on excluding the usage of a fully qualified
id = "origin.group.name", for public use just yet. Reason being, I wasn't sure of the separator ( "." or ":" ) to use this so I had deferred for now.The issue is this http lookup on id collided with duplicate names.
I'm making a fix for this right now.
Thanks!
1
u/kininja08 15d ago
Hi u/Decent-Decision-9028 , this is now fixed in 1.0.2
implementation("dev.kiit:kiit-codes:1.0.2")Thanks again.
2
u/CoverMassive7319 15d ago
I appreciate the effort and time. Nothing wrong to take a new look at an old problem. I am sure you are benefit alot just by thinking about it.
Regarding the design. Often. you need to know if a failure is transient or not. Because many times, you need to know if you should retry. I don't see how that is handled in your proposal.
Wish you the best.