Enum conventions for domain codes including CommonCode interface, EnumType.STRING, and categorization patterns
This document defines rules for categorized domain codes as enums. All domain enums that represent classifiable codes must implement the CommonCode interface from the common module.
Key Principle: All categorized domain codes must implement
CommonCode. Always persist enums asSTRINGin JPA entities — never useORDINAL.
CommonCode in io.glory.common.codes defines the contract for all domain code enums.
interface CommonCode {
val description: String // Human-readable description
val name: String // Enum constant name (provided by Kotlin enum)
}
IMPORTANT: All enums that represent categorized business codes must implement
CommonCode. This approach ensures consistent code/description access across the project and enables automatic REST Docs generation viaCommonCodeDocsTest.
import io.glory.common.codes.CommonCode
enum class OrderStatus(
override val description: String,
) : CommonCode {
PENDING("Order placed, awaiting payment"),
PAID("Payment confirmed"),
SHIPPED("Order shipped"),
DELIVERED("Order delivered"),
CANCELLED("Order cancelled"),
REFUNDED("Order refunded"),
;
}
| Element | Convention | Example |
|---|---|---|
| Enum class | PascalCase, descriptive noun | OrderStatus, PaymentMethod, BookingType |
| Enum constants | SCREAMING_SNAKE_CASE | PENDING, IN_PROGRESS, CREDIT_CARD |
description | Clear English description | "Order placed, awaiting payment" |
IMPORTANT: Add a trailing semicolon (
;) after the last enum constant when the enum has a body (companion object, methods, or properties).
// Good: Trailing semicolon before companion object
enum class UserRole(
override val description: String,
) : CommonCode {
ADMIN("System administrator"),
USER("Regular user"),
GUEST("Guest user"),
; // Required when enum has a body
companion object { ... }
}
// Bad: Missing semicolon
enum class UserRole(...) : CommonCode {
ADMIN("System administrator"),
GUEST("Guest user") // Compilation error if companion follows
companion object { ... }
}
IMPORTANT: Use
@Enumerated(EnumType.STRING)for enum fields in JPA entities. Never useEnumType.ORDINAL— it breaks silently when you reorder or remove enum constants.
@Entity
@Table(name = "orders")
class Order(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
var status: OrderStatus = OrderStatus.PENDING,
) : BaseTimeEntity()
// Bad: ORDINAL stores position index — breaks when enum order changes
@Enumerated(EnumType.ORDINAL)
@Column(nullable = false)
var status: OrderStatus = OrderStatus.PENDING
// DB stores: 0, 1, 2... If PENDING moves to position 2, all data corrupts
Set @Column(length = N) to match the longest enum constant name. This setting prevents truncation and documents the expected range.
// Good: length matches longest constant ("CANCELLED" = 9 chars, use 20 for safety)
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
var status: OrderStatus = OrderStatus.PENDING
// Bad: No length specified — defaults to 255, wastes storage
@Enumerated(EnumType.STRING)
@Column(nullable = false)
var status: OrderStatus = OrderStatus.PENDING
Provide a fromName companion method when external systems send name values that must be mapped to enum constants.
companion object {
fun fromName(name: String): PaymentMethod =
entries.find { it.name == name }
?: throw IllegalArgumentException("Unknown PaymentMethod: $name")
}
Use a nullable variant when the name may not exist and null is an acceptable result.
companion object {
fun fromNameOrNull(name: String): PaymentMethod? =
entries.find { it.name == name }
}
| Scenario | Use CommonCode | Example |
|---|---|---|
| Business status codes | Yes | OrderStatus, BookingStatus |
| Category/type classifications | Yes | PaymentMethod, BookingType |
| Role/permission types | Yes | UserRole, MembershipTier |
| Configuration flags | No | Simple Boolean or constant |
| Internal-only markers | No | Private sealed class or plain enum |
| Response codes | No | Use ResponseCode interface instead |
Do not implement CommonCode for:
ResponseCode (e.g., ErrorCode and SuccessCode)description that are never exposed externally// Good: ResponseCode for HTTP response codes (not CommonCode)
enum class ErrorCode(
override val status: Int,
override val message: String,
) : ResponseCode {
DATA_NOT_FOUND(406, "Data not found"),
}
// Good: Simple enum without CommonCode (internal use only)
enum class SortDirection {
ASC, DESC
}
| Module | Location | Example |
|---|---|---|
| Domain-specific codes | domain/{feature}/entity/ (alongside Entity) | domain/order/entity/OrderStatus.kt |
| Shared across features | domain/common/codes/ | domain/common/codes/Gender.kt |
| Common module codes | common/codes/ | common/codes/response/ErrorCode.kt |
CommonCodeDocsTest automatically documents enums implementing CommonCode. The test generates REST Docs snippets listing all name and description pairs, ensuring API documentation stays in sync with the codebase.
| Pitfall | Problem | Solution |
|---|---|---|
EnumType.ORDINAL | Data corruption on reorder/removal | Always use EnumType.STRING |
Missing CommonCode | Inconsistent code/description access | Implement CommonCode for all domain codes |
No @Column(length) on enum fields | Defaults to 255, wastes storage | Set length to match longest constant |
Missing fromName method | No safe mapping from external name values | Add companion object { fun fromName() } |
| Enum without trailing semicolon | Compilation error when body is added later | Always add ; after last constant |
| Hardcoded string comparisons | Fragile, bypasses type safety | Use enum constants directly |
Before submitting code with enums, verify:
CommonCodedescription property@Enumerated(EnumType.STRING) — never ORDINAL@Column(length = N) is set to a reasonable value for enum fieldsfromName companion method exists when external name mapping is neededdomain/{feature}/entity/ or domain/common/codes/)ResponseCode, not CommonCode