Defining Problem Codes

A ProblemCode is an application-level error code that carries:

  • a stable string identifier (the code field on the response, e.g. PET-2000),

  • a default HTTP status,

  • a human-readable detail, and

  • (optionally) a default ProblemLevel.

Implementing it as an enum keeps every error code declared in one place and lets the compiler enforce that callers reference a known code.

package no.conta.problem.micronaut.example;

import io.micronaut.core.annotation.Nullable;
import io.micronaut.http.HttpStatus;

import no.conta.problem.ProblemCode;

public enum PetProblemCode implements ProblemCode {

    PetNotFound(2000, HttpStatus.NOT_FOUND, "Not possible to find pet");

    private final String code;
    private final Integer status;
    private final String detail;

    PetProblemCode(int code, HttpStatus status, String detail) {
        this.code = "PET-" + code;
        this.status = status.getCode();
        this.detail = detail;
    }

    @Override
    public String getCode() {
        return code;
    }

    @Nullable
    @Override
    public Integer getStatus() {
        return status;
    }

    @Override
    public String getDetail() {
        return detail;
    }
}

The "PET-" prefix is a convention — pick a short, app-specific prefix so codes from different services are distinguishable in logs and dashboards.

Looking up by code

ProblemCode exposes a registry that maps the string code back to the enum constant:

ProblemCode pc = ProblemCode.fromValue("PET-2000", PetProblemCode.class);

Codes have to be registered with ProblemCode.add(…​) before lookups can find them. The convention is a static initializer on the enum:

public enum PetProblemCode implements ProblemCode {

    PetNotFound(2000, HttpStatus.NOT_FOUND, "Not possible to find pet");

    static {
        Arrays.stream(values()).forEach(ProblemCode::add);
    }

    // ...
}

The registry warns on duplicates, so a clash between two enums using the same code surfaces in the logs at startup.