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;
import no.conta.problem.ProblemLevel;

public enum PetProblemCode implements ProblemCode {

    PetNotFound(2000, HttpStatus.NOT_FOUND, "Not possible to find pet"),
    PetAlreadyExists(2001, HttpStatus.CONFLICT, "Pet already exists", ProblemLevel.Warning);

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

    PetProblemCode(int code, HttpStatus status, String detail) {
        this(code, status, detail, null);
    }

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

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

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

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

    @Override
    public ProblemLevel getLevel() {
        return level;
    }
}

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.