Structured exception specification with category owned code prefixes
๐ฏ 1. Key design rules
โ Category owns the code prefix
โ Error defines only a numeric suffix
โ Final error code is derived
โ Error names use camelCase
This enforces semantic ownership and prevents code drift.
๐ง 2. Core concepts
| Concept | Definition |
|---|---|
| Root exception | Single abstract base class |
| Category | Abstract base exception with semantic meaning and code prefix |
| Error | Concrete exception extending one category |
| Code prefix | Category level identifier |
| Code value | Four digit numeric value on the error |
| Final code | <prefix>-<value> |
| Message template | Parameterised human readable message |
| Parameters | Typed constructor arguments |
๐ณ 3. Inheritance model
Rules enforced by the compiler:
- Exactly one root exception
- Categories form an acyclic inheritance tree
- Each category generates one abstract base class
- Each error extends exactly one category
- Only categories define code prefixes
- Only errors define numeric code values
๐ 4. EDL structure
๐งพ 4.1 Top level document
package: com.example.errors
rootException: ApplicationException
๐๏ธ 4.2 Category definition
categories:
NotFound:
codePrefix: NF
httpStatus: 404
retryable: false
Validation:
codePrefix: VAL
httpStatus: 400
Category fields
| Field | Required | Meaning |
|---|---|---|
| parent | No | Parent category, defaults to root |
| codePrefix | Yes | Error code prefix owned by this category |
| httpStatus | No | HTTP status for this category |
| retryable | No | Retryability flag |
| abstract | No | Defaults to true |
โ 4.3 Error definition
errors:
invalidEmail:
category: Validation
code: 0001
message: "Email '{email}' is invalid."
params:
email: String
userNotFound:
category: NotFound
code: 0004
message: "User '{userId}' was not found."
params:
userId: UUID
Error fields
| Field | Required | Meaning |
|---|---|---|
| category | Yes | Owning category |
| code | Yes | Four digit numeric value |
| message | Yes | Message template |
| params | Yes | Named typed parameters |
๐ซ Codes outside 0000 to 9999 are invalid.
๐ 5. Code composition rules
The compiler MUST generate final codes as follows:
<category.codePrefix>-<error.code>
Example
Validation + 0001 -> VAL-0001
NotFound + 0004 -> NF-0004
๐งช 6. Semantic validation rules
๐งฑ Structural
- Category code prefixes must be unique
- Error numeric codes must be unique within a category
- Error names must be camelCase
- Category names must be PascalCase
๐ Message rules
- Every placeholder must match a parameter
- Every parameter must be used exactly once
- Parameter names are case sensitive
โ Java rules
- Generated code must compile without warnings
- All generated exceptions must be immutable
- No reflection is permitted
๐งฌ 7. Java generation semantics
๐ชต 7.1 Root exception
public abstract class ApplicationException extends RuntimeException {
private final String code;
private final String messageTemplate;
private final Map<String, Object> parameters;
protected ApplicationException(
String code,
String messageTemplate,
Map<String, Object> parameters,
Throwable cause) {
super(messageTemplate, cause);
this.code = code;
this.messageTemplate = messageTemplate;
this.parameters = Map.copyOf(parameters);
}
public final String getCode() {
return code;
}
public final String getMessageTemplate() {
return messageTemplate;
}
public final Map<String, Object> getParameters() {
return parameters;
}
}
๐๏ธ 7.2 Category exception
public abstract class ValidationException extends ApplicationException {
protected ValidationException(
String numericCode,
String template,
Map<String, Object> parameters,
Throwable cause) {
super(
"VAL-" + numericCode,
template,
parameters,
cause
);
}
public int getHttpStatus() {
return 400;
}
}
๐ Category composes the final error code.
โ 7.3 Concrete error exception
public final class InvalidEmailException extends ValidationException {
public static final String NUMERIC_CODE = "0001";
public InvalidEmailException(String email) {
super(
NUMERIC_CODE,
"Email '{email}' is invalid.",
Map.of("email", email),
null
);
}
}
โ Leaf class never hardcodes the prefix
โ Prefix changes do not require touching leaf errors
๐ 8. Extended example
๐งพ YAML
categories:
ClientError:
abstract: true
Validation:
parent: ClientError
codePrefix: VAL
httpStatus: 400
NotFound:
parent: ClientError
codePrefix: NF
httpStatus: 404
errors:
invalidEmail:
category: Validation
code: 0001
message: "Email '{email}' is invalid."
params:
email: String
userNotFound:
category: NotFound
code: 0001
message: "User '{userId}' does not exist."
params:
userId: UUID
๐ณ Resulting hierarchy
ApplicationException
โโ ClientErrorException
โโ ValidationException
โ โโ InvalidEmailException -> VAL-0001
โโ NotFoundException
โโ UserNotFoundException -> NF-0001
๐งฐ 9. Maven plugin impact
๐ Additional validations
The Maven plugin MUST now enforce:
- category codePrefix uniqueness
- numeric error codes are exactly 4 digits
- numeric codes are unique per category
- camelCase error identifiers
- PascalCase category identifiers
๐ง 10. Why this refinement matters
- ๐งญ categories fully own their error space
- ๐ prefixes can be renamed without touching errors
- ๐ numeric codes remain compact and sortable
- ๐ error specs stay readable and low noise
- ๐งฌ generated Java stays clean and stable