Skip to main content
Version: Next

GlobalExceptionHandler

Background

In Spring Boot, @ControllerAdvice is used to handle exceptions globally across the entire application. Previously, in the Navida Pro backend, each microservice maintained its own GlobalExceptionHandler, often leading to:

  • Inconsistent error structures across services
  • Different HTTP status codes for the same exception
  • Incompatibility in how the frontend interprets error responses

Problem Example

  • MixedGoals BFF returns one structure for a 500 error
  • Bonus BFF returns a different structure for the same error
  • For session expiry, one service might return 401 Unauthorized, while another returns 500 User Not Found

Due to these inconsistencies, the Frontend cannot reliably parse and act on error responses.


Objective

To solve the above issues:

  • All common exceptions have been moved to a shared GlobalExceptionHandler inside Open Contract
  • A standard response structure is now used across all services
  • Developers are encouraged to handle only custom exceptions at the service level

What Has Been Done

  1. Migrated service-level common exception handling logic to Open Contract
  2. Introduced NavidaErrorResponseHelper for standardized error responses
  3. Designed tenant-specific and status-code-specific error messaging via config

How to Return a Standard Error Response

Step 1: Declare the Helper

private final NavidaErrorResponseHelper errorResponseHelper;

Step 2: Constructor Injection

public CMSCacheController(
CachedChallengeCMSService cachedChallengeCMSService,
LoggerFactoryUtility loggerFactoryUtility,
NavidaErrorResponseHelper errorResponseHelper,
HttpServletRequest request) {

this.cachedChallengeCMSService = cachedChallengeCMSService;
this.businessLogger = loggerFactoryUtility.getLogger(CMSCacheController.class);
this.errorResponseHelper = errorResponseHelper;
this.request = request;
}

Step 3: Use in Catch Block (With Exception)

catch (Exception e) {
log.info("Fetching goal list for AOK ID: {}", aokid);
NavidaErrorResponse errorResponse = errorResponseHelper.buildErrorResponse(e, request, HttpStatus.BAD_REQUEST);
businessLogger.error(true, "Error fetching goal list for AOK ID:{} Exception:{} {}", e, aokid, e.getMessage());

return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(errorResponse);
}

Step 4: Use Without Exception Object

For situations where there's no exception but an error response is still needed (e.g., null result):

NavidaErrorResponse errorResponse = errorResponseHelper.buildErrorResponse(
new Exception("Received null response"), request, HttpStatus.BAD_REQUEST);

Standard Error Response Format

{
"errorMessage": "BadRequest.",
"errorCode": "BAD_REQUEST",
"errorType": "SystemError",
"status": 400,
"path": "/cms-cache-testing/goals",
"timestamp": "2025-06-26T17:54:37.5980658",
"traceId": "a023febdc277d4b77c08b3af2405fc63",
"source": "navida-pro-be-mixedgoals-bff-service"
}

Configurable Error Message Mapping

The errorMessage can be derived from either the exception name or the HTTP status name. These values are tenant-specific and maintained in the properties file.

Example Configuration

aokplus.ArithmeticException=Something went wrong, please try again later.
aokplus.BadRequest=BadRequest.
aokbw.InternalServerError=Our systems currently under maintenance, please try again.

Why Support Both?

Use exception-based config when:

  • You want a consistent message for a specific exception

Use status-based config when:

  • You want the message to vary by HTTP status across different contexts

Example: You may want a specific message for NullPointerException in one scenario, and a more general BadRequest message in another.


Best Practices

  • Use Open Contract GlobalExceptionHandler for all non-custom exceptions
  • Use service-level handler only for custom/user-defined exceptions
  • Always generate error responses using NavidaErrorResponseHelper
  • Log the full exception using businessLogger.error(...) to preserve traceability
  • Keep error messages configurable and tenant-specific

Adopting the Open Contract GlobalExceptionHandler ensures:

  • Consistent behavior across services
  • Better supportability for frontend apps
  • Centralized, configurable, and traceable error handling