Introduction

1. Custom annotation

/**
 * ElementType.METHOD = Can put on individual methods
 * ElementType.TYPE = Can put on entire class
 * RetentionPolicy.RUNTIME = annotation is available at runtime 
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface StdResponseBody{}

2. Standard Response Wrapper

@EqualsAndHashCode
@Data
@NoArgsConstructor
public class StandardResponse<T> {
    private boolean success;
    private T data;
    private ErrorResponse error;

		// success without data
    public static <T> StandardResponse<T> success() {
        StandardResponse<T> response = new StandardResponse<>();
        response.setSuccess(true);
        response.setData(null);
        return response;
    }
    
		// success with data
    public static <T> StandardResponse<T> success(T data) {
        StandardResponse<T> response = new StandardResponse<>();
        response.setSuccess(true);
        response.setData(data);
        return response;
    }

		// error response
    public static <T> StandardResponse<T> error(ErrorResponse error) {
        StandardResponse<T> response = new StandardResponse<>();
        response.setSuccess(false);
        response.setError(error);
        return response;
    }
}

Example of a successful response:

// with data
{
  "success": true,
  "data": {
    "id": 1,
    "name": "Alice"
  },
  "error": null
}

// without data
{
  "success": true,
  "data": null,
  "error": null
}

Example of an error response:

{
  "success": false,
  "data": null,
  "error": {
    "code": "USER_404",
    "message": "User not found",
    "timestamp": "2026-01-25T10:00:00"
  }
}