Exception handling mechanism prompt for spring boot project

 ## Exception Mechanism — Prompt (Spring Boot Microservice)


### Goal

Build a standardized, i18n-enabled exception mechanism in a Spring Boot microservice that returns error responses aligned with HTTP status codes.


---


### Package structure


```

exception/

├── base/

│ ├── BaseErrorService.java (interface)

│ ├── BaseErrorEnum.java (common/base error codes)

│ ├── BaseException.java (abstract exception)

│ ├── BaseErrorResponseDto.java (API response DTO)

│ └── {Specific}Exception.java (optional — domain-specific, extends BaseException)

├── {ServiceName}ErrorEnum.java (project-specific error enum)

├── {ServiceName}Exception.java (general domain exception)

├── {Domain}Exception.java (domain exceptions — as needed)

├── ClientException.java (Feign/external error wrapper — SEPARATE from BaseException)

└── GlobalExceptionHandler.java (@RestControllerAdvice)

```


---


### 1. `BaseErrorService` (interface)


The contract that all error enums must implement:


```java

public interface BaseErrorService {

    String getMessage(); // user-facing message (resolved from i18n)

    int getHttpStatus(); // HTTP status code (400, 404, 409, 500, etc.)

    String getErrorCode(); // unique error code (e.g. "CUSTOMER_NOT_FOUND_ERROR-0003")

}

```


---


### 2. `BaseErrorEnum` (common/base error enum)


**5 common errors** that can stay the same across microservices and are not tied to a specific project:


| Enum constant | Error Code | i18n Key | HTTP Status |

|---|---|---|---|

| BASE_BUSINESS_ERROR | BASE-BUSINESS-ERROR-001 | BASE_BUSINESS_ERROR | 400 |

| BASE_TECH_ERROR | BASE-TECH-ERROR-001 | BASE_TECH_ERROR | 500 |

| BASE_GATEWAY_TIMEOUT | BASE-GATEWAY-TIMEOUT-001 | BASE_GATEWAY_TIMEOUT | 504 |

| BASE_SERVER_ERROR | BASE-SERVER-ERROR-001 | BASE_SERVER_ERROR | 503 |

| BASE_VALIDATION_ERROR | BASE-VALIDATION-ERROR-001 | BASE_VALIDATION_ERROR | 400 |


Each enum entry takes 3 parameters: `(errorCode, messageKey, httpStatus)`.


The `getMessage()` method calls `MessageResolverUtil.getMessage(messageKey)` — so the message is not a plain string, but an **i18n properties file key**.


---


### 3. Project-specific Error Enum (`{ServiceName}ErrorEnum`)


Implements `BaseErrorService`. Same structure as `BaseErrorEnum`, but:


- Base errors are **duplicated** (same 5 entries)

- **Domain-specific errors** are added on top


Example entries:

```

UNAUTHORIZED_ERROR("UNAUTHORIZED-ERROR-0001", "UNAUTHORIZED_ERROR", 400)

RESOURCE_NOT_FOUND_ERROR("RESOURCE_NOT_FOUND_ERROR-0004", "RESOURCE_NOT_FOUND_ERROR", 404)

RESOURCE_ALREADY_EXIST_ERROR("RESOURCE_ALREADY_EXIST_ERROR-0006", "RESOURCE_ALREADY_EXIST_ERROR", 409)

ACCESS_DENIED_ERROR("ACCESS_DENIED_ERROR-0020", "ACCESS_DENIED_ERROR", 403)

```


**Rules:**

- `errorCode` — unique, format: `{ERROR_NAME}-{4-digit number}`

- `message` — key in i18n properties file (usually same as enum name)

- `httpStatus` — aligned with REST semantics (404 not found, 409 conflict, 403 forbidden, 400 bad request)


---


### 4. i18n (MessageSource) configuration


**`ResourceBundleConfig`:**

```java

@Bean

public MessageSource messageSource() {

    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();

    messageSource.setBasename("classpath:i18n/error/messages");

    messageSource.setDefaultEncoding("UTF-8");

    return messageSource;

}

```


**Properties files:** `src/main/resources/i18n/error/`

- `messages_en.properties`

- `messages_az.properties`

- `messages_ru.properties`


Format: `ERROR_KEY=Translated message text`


Example:

```properties

RESOURCE_NOT_FOUND_ERROR=Resource not found

ACCESS_DENIED_ERROR=Access denied

BASE_SERVER_ERROR=A technical error beyond our control has occurred.

```


**`MessageResolverUtil`:**

- Spring `@Component`, `MessageSource` injected in constructor

- Static `getMessage(String key)` method — returns message for current locale via `LocaleContextHolder.getLocale()`

- If key is not found, returns the key itself as fallback


---


### 5. `BaseException` (abstract class)


Extends `RuntimeException`. Parent class for all domain exceptions:


```java

public abstract class BaseException extends RuntimeException {

    public final Object[] args; // for future parameterized i18n messages

    public final BaseErrorService baseErrorService; // error metadata


    // 2 constructors:

    BaseException(BaseErrorService baseErrorService, Object... args)

    BaseException(BaseErrorService baseErrorService, Throwable throwable, Object... args)

}

```


Constructor calls `super(baseErrorService.getMessage())` — so the exception's `getMessage()` returns the i18n-resolved message.


---


### 6. Domain Exception classes


All extend `BaseException` and are simple wrappers:


| Exception | Purpose |

|---|---|

| `{ServiceName}Exception` | General domain errors (most commonly used) |

| `{DomainA}Exception` | Errors for a specific domain/business area |

| `{DomainB}Exception` | Errors for another domain/business area |

| `{Specific}Exception` | Specific error type (optional, can live in base package) |


Each constructor: `(BaseErrorService errorService, Object... args)`


**Usage in service layer:**

```java

throw new {ServiceName}Exception(RESOURCE_NOT_FOUND_ERROR);

throw new {DomainA}Exception(RESOURCE_ALREADY_EXIST_ERROR);

throw new {ServiceName}Exception(BaseErrorEnum.BASE_SERVER_ERROR); // generic error can also be used

```


---


### 7. `BaseErrorResponseDto` (API response record)


```java

public record BaseErrorResponseDto(

    String code, // error code

    String message, // user-facing message

    String path, // request path

    String timestamp, // LocalDateTime.now().toString()

    Integer status, // HTTP status

    Object... data // extra data (Map<String,String> for validation errors)

) {}

```


Used both as the **API response format** and to **parse errors from Feign clients**.


---


### 8. `GlobalExceptionHandler` (@RestControllerAdvice)


Catches all exceptions and returns a standardized `BaseErrorResponseDto` response.


**Handlers:**


| Handler | Exception type | Notes |

|---|---|---|

| `handleBaseException` | `BaseException` | Generic handler — exceptions without a dedicated handler fall here |

| `handleValidationException` | `MethodArgumentNotValidException` | Validation error — field errors returned as Map in `data`, uses `BASE_VALIDATION_ERROR` code |

| `handle{ServiceName}Exception` | `{ServiceName}Exception` | Domain exception handler |

| `handle{DomainA}Exception` | `{DomainA}Exception` | Domain exception handler (optional) |


**Each handler follows the same pattern:**

```java

return new ResponseEntity<>(

    new BaseErrorResponseDto(

        ex.baseErrorService.getErrorCode(),

        ex.getMessage(),

        webRequest.getContextPath(),

        LocalDateTime.now().toString(),

        ex.baseErrorService.getHttpStatus()

    ),

    HttpStatusCode.valueOf(ex.baseErrorService.getHttpStatus())

);

```


**Validation handler is special:** passes a field→error message Map in the `data` parameter.


---


### 9. `ClientException` (Feign/external error — SEPARATE hierarchy)


Does **not** extend `BaseException`. Wraps errors from external services:


```java

public class ClientException extends RuntimeException {

    private final transient BaseErrorResponseDto baseErrorResponse;

}

```


**No handler in `GlobalExceptionHandler`** — this exception is caught in the service layer and does not propagate directly to the API.


---


### 10. `FeignClientErrorDecoder` (Feign error handling)


Parses Feign client error responses:


1. Reads response body as `BaseErrorResponseDto`

2. If external APIs use a different error format, maps them to `BaseErrorResponseDto`

3. Returns `ClientException(errorDetail)`

4. If parsing fails → fallback to `{ServiceName}Exception(BaseErrorEnum.BASE_SERVER_ERROR)`


**ClientException handling in service layer:**

```java

String errorDetail = switch (e) {

    case ClientException ce -> ce.getBaseErrorResponse().message();

    case {ServiceName}Exception se -> se.baseErrorService.getMessage();

    default -> e.getMessage();

};

```


---


### Full flow diagram


```

[Service Layer]

    │

    ├─ throw new {ServiceName}Exception(ERROR_ENUM)

    │ ↓

    │ [GlobalExceptionHandler]

    │ ↓

    │ BaseErrorResponseDto { code, message, path, timestamp, status }

    │ ↓

    │ HTTP Response (404/400/409/...)

    │

    └─ feignClient.someCall()

            ↓ (error response)

        [FeignClientErrorDecoder]

            ↓

        ClientException(BaseErrorResponseDto)

            ↓

        [Service catch block]

            ↓

        error detail stored / mapped to domain exception

```


---


### Checklist for setting up in a new project


1. **Create `exception/base/` package:**

   - `BaseErrorService` interface

   - `BaseErrorEnum` (5 common errors)

   - `BaseException` abstract class

   - `BaseErrorResponseDto` record


2. **Create project-specific error enum** (`{ServiceName}ErrorEnum implements BaseErrorService`)


3. **Configure i18n:**

   - `ResourceBundleConfig` → `MessageSource` bean

   - `MessageResolverUtil` → static message resolver

   - `messages_en/az/ru.properties` files


4. **Create domain exception classes** (extend `BaseException`)


5. **Create `GlobalExceptionHandler`** (`@RestControllerAdvice`):

   - Generic `BaseException` handler

   - `MethodArgumentNotValidException` validation handler

   - Optional dedicated handlers for domain exceptions (same pattern)


6. **Feign error handling (optional):**

   - `ClientException` (SEPARATE from `BaseException`)

   - `FeignClientErrorDecoder` → parse response into `BaseErrorResponseDto`


7. **Usage in service layer:**

   ```java

   throw new {Domain}Exception({ServiceName}ErrorEnum.SOME_ERROR);

   ```


---

Comments

Popular posts from this blog

Installation instructions for some programs on linux ubuntu

Hibernate and Application Performance

write huge text into clob column in oracle