java 135 lines · 4 tabs

Consistent JSON Error Responses with @RestControllerAdvice in Spring Boot

Shared by codesnips Jul 2026
4 tabs
package com.example.api.error;

import com.fasterxml.jackson.annotation.JsonInclude;

import java.time.Instant;
import java.util.List;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record ApiError(
        int status,
        String code,
        String message,
        String path,
        Instant timestamp,
        List<FieldError> fieldErrors
) {
    public ApiError {
        if (fieldErrors == null) {
            fieldErrors = List.of();
        }
    }

    public record FieldError(String field, String reason) {
    }
}
4 files · java Explain with highlit

This snippet shows how a Spring Boot API can return one predictable error shape for every failure, whether it comes from validation, a missing resource, or an unexpected bug. Centralizing this logic in a @RestControllerAdvice keeps controllers clean and guarantees clients never have to guess the format of an error body.

The ApiError response tab defines the immutable payload every error is serialized into. It carries a machine-readable code, a human message, the request path, an ISO timestamp, and an optional list of fieldErrors for validation failures. The nested FieldError record captures which field failed and why, so front-end forms can highlight inputs precisely. Because fieldErrors defaults to an empty list, non-validation errors serialize cleanly without a null field.

The ResourceNotFoundException tab is a small domain exception that extends RuntimeException. Modeling it as its own type lets the handler map it to a 404 and a stable RESOURCE_NOT_FOUND code, rather than leaking a generic 500. Service code throws it naturally when a lookup returns nothing.

The GlobalExceptionHandler tab is where the pattern lives. Each @ExceptionHandler method translates one exception category into an ApiError with the correct HttpStatus. handleNotFound returns 404; handleValidation intercepts Spring's MethodArgumentNotValidException, walks BindingResult, and builds a FieldError for every rejected value; handleConstraintViolation does the same for @Validated path and query params. The catch-all handleUnexpected logs the stack trace but returns a deliberately vague message, so internal details are never exposed to callers. A shared build helper assembles the response so status, code, and path are always populated the same way.

The UserController tab shows the advice in action: @Valid on the request body triggers the validation handler automatically, and the orElseThrow throwing ResourceNotFoundException routes into handleNotFound. Notice the controller contains zero try/catch blocks — that separation is the whole point.

A key trade-off is that broad advice can accidentally swallow errors, so ordering from specific to general matters, and the catch-all should stay last. This approach scales well: adding a new error type means adding one handler method, and every client keeps seeing the same JSON contract.


Related snips

typescript
export interface RetryOptions {
  retries: number;
  baseMs: number;
  maxMs: number;
  signal?: AbortSignal;
  onRetry?: (attempt: number, delay: number, err: unknown) => void;

Exponential backoff with jitter for retries

typescript reliability retry
by codesnips 2 tabs
graphql
type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
    createdAt: String!

GraphQL API with Spring Boot

java graphql spring-boot
by David Kumar 3 tabs
ruby
class SignupForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :account_name, :string
  attribute :email, :string

Shallow Controller, Deep Params: Form Object Pattern

rails activemodel form-object
by codesnips 3 tabs
javascript
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
  const success = true;

  setTimeout(() => {
    if (success) {

Promises and async/await patterns for asynchronous JavaScript

javascript promises async-await
by Alex Chang 1 tab
java
package com.example.starter.config;

import com.example.starter.properties.CustomProperties;
import com.example.starter.service.CustomService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

Custom Spring Boot starters

java spring-boot starter
by David Kumar 4 tabs
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Form Validation Example</title>
  <style>

HTML forms with validation and accessibility

html forms validation
by Alex Chang 1 tab

Share this code

Here's the card — post it anywhere.

Consistent JSON Error Responses with @RestControllerAdvice in Spring Boot — share card
Link copied