Skip to content
Harveer Narwar
Back to all articles
•2 min read

Enterprise Exception Architecture with @ControllerAdvice and TMF Standards

Standardizing API error contracts across 650+ downstream systems using Spring Boot @RestControllerAdvice and TeleManagement Forum specifications.

#Spring Boot#REST APIs#Enterprise#System Design
Read on Medium →

The Chaos of Inconsistent Errors

In large enterprise microservices platforms integrated with hundreds of downstream services, erratic error contracts are among the leading causes of production outages.

When an unexpected exception occurs, default framework behavior often returns raw stack traces or vague HTTP 500 responses:

{
  "timestamp": "2026-08-14T10:00:00.000+00:00",
  "status": 500,
  "error": "Internal Server Error",
  "path": "/api/v1/orders/ORD-9821/milestones"
}

Downstream systems cannot determine whether the error was transient (eligible for retry) or fatal (requiring manual intervention), leading to misrouted alerts and cascading failures.

Standardizing with TMF-Compliant Contracts

The TM Forum (TMF) REST API Design Guidelines mandate structured error schemas:

{
  "code": "ERR-B2B-40401",
  "reason": "EntityNotFound",
  "message": "Milestone identifier 'MS-881' does not exist for Order 'ORD-9821'",
  "status": "404",
  "referenceError": "https://specs.telecom.org/errors/ERR-B2B-40401",
  "timestamp": "2026-08-14T10:00:00.120Z"
}

Centralized Handling in Spring Boot

We configure a centralized @RestControllerAdvice that translates both business domain exceptions and framework errors into compliant envelopes:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MilestoneNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(MilestoneNotFoundException ex) {
        ErrorResponse error = ErrorResponse.builder()
            .code("ERR-B2B-40401")
            .reason("EntityNotFound")
            .message(ex.getMessage())
            .status(HttpStatus.NOT_FOUND.value())
            .timestamp(Instant.now())
            .build();
            
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    @ExceptionHandler(OptimisticLockException.class)
    public ResponseEntity<ErrorResponse> handleConflict(OptimisticLockException ex) {
        ErrorResponse error = ErrorResponse.builder()
            .code("ERR-B2B-40902")
            .reason("ConcurrentModification")
            .message("Resource was modified by another operation. Retry eligible.")
            .status(HttpStatus.CONFLICT.value())
            .timestamp(Instant.now())
            .build();
            
        return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
    }
}

Providing standardized machine-readable error codes (ERR-B2B-40902) enables downstream API gateways to automatically classify retryable errors vs non-retryable 4xx client errors.

Results

  • Reduced downstream triage time by over 60%.
  • Eliminated ambiguous HTTP 500 errors across 10+ core endpoints.
  • Predictable contract testing validated using automated Postman Newman test suites in CI pipelines.