Coupling/Decoupling domain exceptions from HTTP semantics

1 day ago 1
ARTICLE AD BOX

I’m designing error handling in a Spring Boot REST API and I’m unsure about the proper separation of concerns between the domain layer and the web layer.

I currently have a base DomainException with subclasses like ExpiredOrderException.

Option 1 – Keep HTTP out of the domain

Domain exceptions only contain domain information (code, args, defaultMessage), and the controller advice decides the HTTP status:

public class DomainException extends RuntimeException { private final String code; private final Object[] args; private final String defaultMessage; public DomainException(String code, String defaultMessage, Object... args) { super(defaultMessage); this.code = code; this.defaultMessage = defaultMessage; this.args = args; } public String getCode() { return code; } public String getDefaultMessage() { return defaultMessage; } public Object[] getArgs() { return args; } } public class ExpiredOrderException extends DomainException { public ExpiredOrderException(Object... args) { super( "error.order.expired", "This order is already expired.", args ); } } @ExceptionHandler({ ExpiredOrderException.class // add other domain exceptions here }) public ResponseEntity<ProblemDetail> handleBadRequest(DomainException ex) { Locale locale = LocaleContextHolder.getLocale(); String localizedMessage = messageSource.getMessage( ex.getCode(), ex.getArgs(), ex.getDefaultMessage(), locale ); ProblemDetail pd = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST); pd.setTitle(ex.getCode()); pd.setDetail(localizedMessage); pd.setProperty("args", ex.getArgs()); pd.setProperty("locale", locale.toLanguageTag()); return ResponseEntity.badRequest().body(pd); }

Here, the domain layer is completely unaware of HTTP.
But introduces a pain point listing exception classes.


Option 2 – Put HTTP status inside the exception

The domain exception carries the HTTP status code:

public class DomainException extends RuntimeException { private final int httpCode; private final String code; private final Object[] args; private final String defaultMessage; public DomainException(int httpCode, String code, String defaultMessage, Object... args) { super(code); this.httpCode = httpCode; this.code = code; this.args = args; this.defaultMessage = defaultMessage; } public int getHttpCode() { return httpCode; } public String getCode() { return code; } public String getDefaultMessage() { return defaultMessage; } public Object[] getArgs() { return args; } } public class ExpiredOrderException extends DomainException { public ExpiredOrderException(Object... args) { super(400, "error.order.expired", "This order is already expired.", args); } } @ExceptionHandler(DomainException.class) public ProblemDetail handleDomainExceptions(DomainException ex) { Locale locale = LocaleContextHolder.getLocale(); String localizedMessage = messageSource.getMessage( ex.getCode(), ex.getArgs(), ex.getDefaultMessage(), locale ); ProblemDetail pd = ProblemDetail.forStatus(ex.getHttpCode()); pd.setTitle(ex.getCode()); pd.setDetail(localizedMessage); return pd; }

Carrying the HTTP status code inside the exception is more convenient, but I’m concerned about clean architecture. This approach seems to couple the domain layer to HTTP semantics.


My Questions

From a clean architecture, is it considered a bad practice for domain exceptions to carry HTTP status codes?

In general is there a recommended pattern in Spring Boot for mapping domain exceptions to HTTP responses?

Read Entire Article