From 1fd959c58452815e15edb9416a9be8dc1f404684 Mon Sep 17 00:00:00 2001
From: Pasindu Owa Gamage
Date: Fri, 27 Feb 2026 20:56:07 +0530
Subject: [PATCH 1/5] Added error methods to ApiResponse
---
.../io/github/og4dev/dto/ApiResponse.java | 80 ++++++++++++++++---
.../io/github/og4dev/dto/package-info.java | 17 ++--
2 files changed, 76 insertions(+), 21 deletions(-)
diff --git a/src/main/java/io/github/og4dev/dto/ApiResponse.java b/src/main/java/io/github/og4dev/dto/ApiResponse.java
index 3f127a4..36d202f 100644
--- a/src/main/java/io/github/og4dev/dto/ApiResponse.java
+++ b/src/main/java/io/github/og4dev/dto/ApiResponse.java
@@ -17,10 +17,10 @@
* The response structure follows a standardized format:
*
*
- * status - HTTP status code (200, 201, 404, etc.)
- * message - Human-readable description of the response
- * content - The response payload (generic type T, optional)
- * timestamp - RFC 3339 UTC timestamp (auto-generated)
+ * status - HTTP status code (200, 201, 404, etc.)
+ * message - Human-readable description of the response
+ * content - The response payload (generic type T, optional)
+ * timestamp - RFC 3339 UTC timestamp (auto-generated)
*
*
* Thread Safety: This class is immutable and thread-safe. All fields are final and set during
@@ -129,10 +129,25 @@ public Instant getTimestamp() {
return timestamp;
}
+ /**
+ * Creates a CREATED (201) response with a message.
+ *
+ * @param the type of the response content
+ * @param message the response message
+ * @return a ResponseEntity with CREATED status
+ */
+ public static ResponseEntity> created(String message) {
+ return ResponseEntity.status(HttpStatus.CREATED)
+ .body(new ApiResponseBuilder()
+ .status(HttpStatus.CREATED.value())
+ .message(message)
+ .build());
+ }
+
/**
* Creates a CREATED (201) response with a message and content.
*
- * @param the type of the response content
+ * @param the type of the response content
* @param message the response message
* @param content the response content
* @return a ResponseEntity with CREATED status
@@ -163,7 +178,7 @@ public static ResponseEntity> success(String message) {
/**
* Creates a SUCCESS (200) response with a message and content.
*
- * @param the type of the response content
+ * @param the type of the response content
* @param message the response message
* @param content the response content
* @return a ResponseEntity with OK status
@@ -181,7 +196,7 @@ public static ResponseEntity> success(String message, T conte
* Creates a response with a custom HTTP status and message only.
*
* @param message the response message
- * @param status the HTTP status
+ * @param status the HTTP status
* @return a ResponseEntity with the specified status
*/
public static ResponseEntity> status(String message, HttpStatus status) {
@@ -195,10 +210,10 @@ public static ResponseEntity> status(String message, HttpStatu
/**
* Creates a response with a custom HTTP status, message, and content.
*
- * @param the type of the response content
+ * @param the type of the response content
* @param message the response message
* @param content the response content
- * @param status the HTTP status
+ * @param status the HTTP status
* @return a ResponseEntity with the specified status
*/
public static ResponseEntity> status(String message, T content, HttpStatus status) {
@@ -211,22 +226,63 @@ public static ResponseEntity> status(String message, T conten
}
/**
- * Builder class for constructing ApiResponse instances.
+ * Creates a response with a custom HTTP status and error message only.
+ *
+ * @param message the error response message
+ * @param status the HTTP status
+ * @return a ResponseEntity with the specified status
+ */
+ public static ResponseEntity> error(String message, HttpStatus status) {
+ return ResponseEntity.status(status)
+ .body(new ApiResponseBuilder()
+ .status(status.value())
+ .message(message)
+ .build());
+ }
+
+ /**
+ * Creates a response with a custom HTTP status, error message, and content.
+ *
+ * @param the type of the response content
+ * @param message the error response message
+ * @param content the response content
+ * @param status the HTTP status
+ * @return a ResponseEntity with the specified status
+ */
+ public static ResponseEntity> error(String message, T content, HttpStatus status) {
+ return ResponseEntity.status(status)
+ .body(new ApiResponseBuilder()
+ .status(status.value())
+ .message(message)
+ .content(content)
+ .build());
+ }
+
+ /**
+ * Builder class for constructing {@link ApiResponse} instances.
*
* @param the type of the response content
*/
public static class ApiResponseBuilder {
+
private Integer status;
private String message;
private T content;
+ /**
+ * Default constructor for creating an empty builder.
+ */
+ public ApiResponseBuilder() {
+ // Default constructor
+ }
+
/**
* Sets the HTTP status code.
*
* @param status the status code
* @return this builder instance
*/
- private ApiResponseBuilder status(Integer status) {
+ public ApiResponseBuilder status(Integer status) {
this.status = status;
return this;
}
@@ -254,7 +310,7 @@ public ApiResponseBuilder content(T content) {
}
/**
- * Builds the ApiResponse instance.
+ * Builds the {@link ApiResponse} instance.
*
* @return a new ApiResponse instance
*/
diff --git a/src/main/java/io/github/og4dev/dto/package-info.java b/src/main/java/io/github/og4dev/dto/package-info.java
index 026b729..a733fe1 100644
--- a/src/main/java/io/github/og4dev/dto/package-info.java
+++ b/src/main/java/io/github/og4dev/dto/package-info.java
@@ -1,24 +1,23 @@
/**
- * Data Transfer Object (DTO) classes for standardized API responses.
+ * Provides Data Transfer Object (DTO) classes for standardized API responses.
*
* This package contains classes for structuring API responses in a consistent, type-safe format.
* The main class {@link io.github.og4dev.dto.ApiResponse} provides a generic wrapper that includes:
*
*
- * HTTP status code
- * Human-readable message
- * Response content (generic type T)
- * Automatic UTC timestamp generation
+ * HTTP status code
+ * Human-readable message
+ * Response content (generic type {@code T})
+ * Automatic UTC timestamp generation
*
*
* All response objects are immutable and thread-safe, using a builder pattern for flexible construction.
- * Factory methods like {@code success()}, {@code created()}, and {@code status()} provide convenient
- * ways to create responses without manual building.
+ * Factory methods like {@code success()}, {@code created()}, {@code status()}, and {@code error()}
+ * provide convenient ways to create responses without manual building.
*
*
* @author Pasindu OG
* @version 1.3.0
* @since 1.0.0
*/
-package io.github.og4dev.dto;
-
+package io.github.og4dev.dto;
\ No newline at end of file
From 5d428f0a83f6a505b5476c343a72f3e174469bf2 Mon Sep 17 00:00:00 2001
From: Pasindu Owa Gamage
Date: Fri, 27 Feb 2026 23:49:01 +0530
Subject: [PATCH 2/5] Created AutoResponse annotation and GlobalResponseWrapper
for autoconfiguring API response sending
---
.../og4dev/advice/GlobalResponseWrapper.java | 107 ++++++++++++++++++
.../og4dev/annotation/AutoResponse.java | 9 ++
2 files changed, 116 insertions(+)
create mode 100644 src/main/java/io/github/og4dev/advice/GlobalResponseWrapper.java
create mode 100644 src/main/java/io/github/og4dev/annotation/AutoResponse.java
diff --git a/src/main/java/io/github/og4dev/advice/GlobalResponseWrapper.java b/src/main/java/io/github/og4dev/advice/GlobalResponseWrapper.java
new file mode 100644
index 0000000..ed8deb0
--- /dev/null
+++ b/src/main/java/io/github/og4dev/advice/GlobalResponseWrapper.java
@@ -0,0 +1,107 @@
+package io.github.og4dev.advice;
+
+import io.github.og4dev.annotation.AutoResponse;
+import io.github.og4dev.dto.ApiResponse;
+import org.jspecify.annotations.Nullable;
+import org.springframework.core.MethodParameter;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ProblemDetail;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.http.server.ServletServerHttpResponse;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
+
+/**
+ * Global response interceptor that automatically wraps REST controller outputs into the
+ * standardized {@link ApiResponse} format.
+ *
+ * This wrapper is conditionally activated only for controllers or specific methods
+ * annotated with the {@link AutoResponse @AutoResponse} annotation. It provides a seamless
+ * developer experience by eliminating the need to manually return {@code ResponseEntity>}
+ * from every controller method.
+ *
+ * Core Functionalities:
+ *
+ * Automatic Encapsulation: Intercepts raw DTOs, Lists, or primitive responses and packages
+ * them into the {@code content} field of an {@code ApiResponse}.
+ * Status Code Preservation: Dynamically reads the current HTTP status of the response
+ * (e.g., set via {@code @ResponseStatus(HttpStatus.CREATED)}) and ensures it is accurately
+ * reflected in the final {@code ApiResponse}.
+ * Safety Mechanisms: Intelligently skips wrapping if the response is already formatted
+ * to prevent double-wrapping errors or interference with standard error handling protocols.
+ *
+ *
+ * @author Pasindu OG
+ * @version 1.4.0
+ * @see AutoResponse
+ * @see ApiResponse
+ * @see ResponseBodyAdvice
+ * @since 1.4.0
+ */
+@RestControllerAdvice(annotations = AutoResponse.class)
+@SuppressWarnings("unused")
+public class GlobalResponseWrapper implements ResponseBodyAdvice {
+
+ /**
+ * Determines whether the current response should be intercepted and wrapped.
+ *
+ * To guarantee application stability and adherence to standard HTTP protocols, this method
+ * specifically excludes the following return types from being wrapped:
+ *
+ *
+ * {@link ApiResponse} - Prevents recursive double-wrapping (e.g., {@code ApiResponse>}).
+ * {@link ResponseEntity} - Skips manual responses to respect developer's explicit configurations.
+ * {@link ProblemDetail} - Excludes RFC 9457 error responses generated by exception handlers.
+ * {@link String} - Bypasses raw strings to prevent {@code ClassCastException} when Spring utilizes
+ * the {@code StringHttpMessageConverter}.
+ *
+ *
+ * @param returnType The return type of the controller method.
+ * @param converterType The selected HTTP message converter.
+ * @return {@code true} if the body should be wrapped; {@code false} if it should be skipped.
+ */
+ @Override
+ public boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType) {
+ Class> type = returnType.getParameterType();
+ return !ApiResponse.class.isAssignableFrom(type) &&
+ !ResponseEntity.class.isAssignableFrom(type) &&
+ !ProblemDetail.class.isAssignableFrom(type) &&
+ !String.class.isAssignableFrom(type);
+ }
+
+ /**
+ * Intercepts the response body before it is written to the output stream and encapsulates it
+ * within an {@link ApiResponse}.
+ *
+ * This method extracts the actual HTTP status code set on the current response (defaulting to 200 OK).
+ * Based on whether the status code represents a success (2xx) or another state, it dynamically
+ * assigns an appropriate message ("Success" or "Processed") to the API response.
+ *
+ *
+ * @param body The raw object returned by the controller method.
+ * @param returnType The return type of the controller method.
+ * @param selectedContentType The selected content type for the response.
+ * @param selectedConverterType The selected HTTP message converter.
+ * @param request The current server HTTP request.
+ * @param response The current server HTTP response.
+ * @return The newly wrapped {@code ApiResponse} object ready to be serialized.
+ */
+ @Override
+ public @Nullable Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class extends HttpMessageConverter>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
+ int statusCode = HttpStatus.OK.value();
+
+ // Extract the dynamically set HTTP status code (e.g., from @ResponseStatus)
+ if (response instanceof ServletServerHttpResponse serverHttpResponse) {
+ statusCode = serverHttpResponse.getServletResponse().getStatus();
+ }
+
+ HttpStatus httpStatus = HttpStatus.valueOf(statusCode);
+
+ // Wrap the payload and generate the final format
+ return ApiResponse.status(httpStatus.is2xxSuccessful() ? "Success" : "Processed", body, httpStatus).getBody();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/og4dev/annotation/AutoResponse.java b/src/main/java/io/github/og4dev/annotation/AutoResponse.java
new file mode 100644
index 0000000..d22eb84
--- /dev/null
+++ b/src/main/java/io/github/og4dev/annotation/AutoResponse.java
@@ -0,0 +1,9 @@
+package io.github.og4dev.annotation;
+
+import java.lang.annotation.*;
+
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface AutoResponse {
+}
From 5fdbc0c9158077e3f1051d947094a366b68411bd Mon Sep 17 00:00:00 2001
From: Pasindu Owa Gamage
Date: Sat, 28 Feb 2026 00:16:03 +0530
Subject: [PATCH 3/5] feat: introduce @AutoResponse for zero-boilerplate
response wrapping
- Add `@AutoResponse` annotation for opt-in controller response wrapping
- Implement `GlobalResponseWrapper` to intercept and wrap raw DTOs into `ApiResponse`
- Preserve custom HTTP status codes set via `@ResponseStatus`
- Safely bypass `ResponseEntity`, `ApiResponse`, `ProblemDetail`, and `String` to prevent double-wrapping
- Update `ApiResponseAutoConfiguration` to register the new wrapper automatically
- Add comprehensive Javadocs and `package-info.java` for the new `advice` package
- Update README.md with v1.4.0 features, examples, and version bumps
---
README.md | 3695 ++---------------
pom.xml | 2 +-
.../og4dev/advice/GlobalResponseWrapper.java | 5 +-
.../io/github/og4dev/advice/package-info.java | 30 +
.../og4dev/annotation/AutoResponse.java | 50 +-
.../io/github/og4dev/annotation/AutoTrim.java | 2 +-
.../io/github/og4dev/annotation/XssCheck.java | 2 +-
.../og4dev/annotation/package-info.java | 2 +-
.../config/ApiResponseAutoConfiguration.java | 122 +-
.../io/github/og4dev/config/package-info.java | 7 +-
.../io/github/og4dev/dto/ApiResponse.java | 2 +-
.../io/github/og4dev/dto/package-info.java | 2 +-
.../github/og4dev/exception/ApiException.java | 2 +-
.../exception/GlobalExceptionHandler.java | 2 +-
.../github/og4dev/exception/package-info.java | 2 +-
.../github/og4dev/filter/TraceIdFilter.java | 2 +-
.../io/github/og4dev/filter/package-info.java | 2 +-
.../java/io/github/og4dev/package-info.java | 4 +-
18 files changed, 413 insertions(+), 3522 deletions(-)
create mode 100644 src/main/java/io/github/og4dev/advice/package-info.java
diff --git a/README.md b/README.md
index 2cfb457..5b19353 100644
--- a/README.md
+++ b/README.md
@@ -3,186 +3,175 @@
# OG4Dev Spring API Response
-
+
- A lightweight, zero-configuration REST API Response wrapper for Spring Boot applications
+A lightweight, zero-configuration REST API Response wrapper for Spring Boot applications
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-A lightweight, type-safe API Response wrapper for Spring Boot applications. Standardize your REST API responses with consistent structure, automatic timestamps, distributed tracing support, and clean factory methods. Features zero-configuration Spring Boot auto-configuration and production-ready exception handling with comprehensive RFC 9457 ProblemDetail support covering 10 common error scenarios. No external dependencies required - uses pure Java with a custom builder pattern.
+A lightweight, type-safe API Response wrapper for Spring Boot applications. Standardize your REST API responses with consistent structure, automatic timestamps, distributed tracing support, and clean factory methods. Features zero-configuration Spring Boot auto-configuration, opt-in automatic response wrapping (@AutoResponse), and production-ready exception handling with comprehensive RFC 9457 ProblemDetail support covering 10 common error scenarios. No external dependencies required - uses pure Java with a custom builder pattern.
-
## ๐ Quick Links
-- ๐ฆ [Maven Central Repository](https://central.sonatype.com/artifact/io.github.og4dev/og4dev-spring-response)
-- ๐ [JavaDoc Documentation](https://javadoc.io/doc/io.github.og4dev/og4dev-spring-response)
-- ๐ [Report Issues](https://github.com/OG4Dev/og4dev-spring-response/issues)
-- ๐ก [Feature Requests](https://github.com/OG4Dev/og4dev-spring-response/issues/new)
-- ๐ค [Contributing Guide](#-contributing)
+* ๐ฆ [Maven Central Repository](https://central.sonatype.com/artifact/io.github.og4dev/og4dev-spring-response)
+* ๐ [JavaDoc Documentation](https://javadoc.io/doc/io.github.og4dev/og4dev-spring-response)
+* ๐ [Report Issues](https://github.com/OG4Dev/og4dev-spring-response/issues)
+* ๐ก [Feature Requests](https://github.com/OG4Dev/og4dev-spring-response/issues/new)
+* ๐ค [Contributing Guide](#-contributing)
## ๐ Table of Contents
-- [Quick Links](#-quick-links)
-- [Key Highlights](#-key-highlights)
-- [Features](#-features)
-- [Requirements](#-requirements)
-- [What Makes This Different?](#-what-makes-this-different)
-- [Installation](#-installation)
-- [Project Structure](#-project-structure)
-- [Quick Start](#-quick-start)
-- [Auto-Configuration](#๏ธ-auto-configuration)
-- [Built-in Security Features](#-built-in-security-features)
-- [Built-in Exception Handling](#๏ธ-built-in-exception-handling)
-- [Usage](#-usage)
-- [Real-World Examples](#-real-world-examples)
-- [API Reference](#-api-reference)
-- [Response Structure](#-response-structure)
-- [Best Practices](#-best-practices)
-- [Testing](#-testing)
-- [Architecture & Design](#๏ธ-architecture--design-principles)
-- [OpenAPI/Swagger Integration](#-openapiswagger-integration)
-- [Compatibility Matrix](#-compatibility-matrix)
-- [Troubleshooting](#-troubleshooting)
-- [FAQ](#-faq)
-- [Performance & Best Practices](#-performance--best-practices)
-- [Migration Guide](#-migration-guide)
-- [Security Considerations](#-security-considerations)
-- [Contributing](#-contributing)
-- [License](#-license)
-- [Contact](#-contact)
-- [Acknowledgments](#-acknowledgments)
-- [Version History](#-version-history)
+* [Quick Links](#-quick-links)
+* [Key Highlights](#-key-highlights)
+* [Features](#-features)
+* [Requirements](#-requirements)
+* [What Makes This Different?](#-what-makes-this-different)
+* [Installation](#-installation)
+* [Project Structure](#-project-structure)
+* [Quick Start](#-quick-start)
+* [Auto-Configuration](#%EF%B8%8F-auto-configuration)
+* [Opt-in Automatic Wrapping (@AutoResponse)](#-opt-in-automatic-wrapping-autoresponse)
+* [Built-in Security Features](#-built-in-security-features)
+* [Built-in Exception Handling](#%EF%B8%8F-built-in-exception-handling)
+* [Usage](#-usage)
+* [Real-World Examples](#-real-world-examples)
+* [API Reference](#-api-reference)
+* [Response Structure](#-response-structure)
+* [Best Practices](#-best-practices)
+* [Testing](#-testing)
+* [Architecture & Design](#%EF%B8%8F-architecture--design-principles)
+* [OpenAPI/Swagger Integration](#-openapiswagger-integration)
+* [Compatibility Matrix](#-compatibility-matrix)
+* [Troubleshooting](#-troubleshooting)
+* [FAQ](#-faq)
+* [Performance & Best Practices](#-performance--best-practices)
+* [Migration Guide](#-migration-guide)
+* [Security Considerations](#-security-considerations)
+* [Contributing](#-contributing)
+* [License](#-license)
+* [Contact](#-contact)
+* [Acknowledgments](#-acknowledgments)
+* [Version History](#-version-history)
## ๐ฏ Key Highlights
-- ๐ **Truly Zero Configuration** - Spring Boot 3.x/4.x auto-configuration with META-INF imports
-- ๐ฏ **Production-Ready** - Built-in RFC 9457 ProblemDetail with 10 comprehensive exception handlers
-- ๐ก๏ธ **Complete Error Coverage** - Handles validation, JSON parsing, 404s, method mismatches, media types, and more
-- ๐ **Trace IDs in Errors** - Error responses include traceId for debugging
-- ๐ **Type-Safe & Immutable** - Thread-safe design with generic type support
-- ๐ฆ **Ultra-Lightweight** - Only ~10KB JAR size with provided dependencies
-- ๐ **Microservices-Ready** - Built-in trace IDs for distributed tracing
-- โ
**Battle-Tested** - Used in production Spring Boot applications
-- ๐ **Professional-Grade Javadoc** - 100% coverage with comprehensive method documentation
-- ๐ **Opt-in Security Features** - Fine-grained control with field-level annotations
-- ๐ซ **Zero External Dependencies** - Pure Java, no Lombok required
+* ๐ **Truly Zero Configuration** - Spring Boot 3.x/4.x auto-configuration with META-INF imports
+* ๐ **Zero Boilerplate** - Opt-in `@AutoResponse` to automatically wrap raw return types
+* ๐ฏ **Production-Ready** - Built-in RFC 9457 ProblemDetail with 10 comprehensive exception handlers
+* ๐ก๏ธ **Complete Error Coverage** - Handles validation, JSON parsing, 404s, method mismatches, media types, and more
+* ๐ **Trace IDs in Errors** - Error responses include traceId for debugging
+* ๐ **Type-Safe & Immutable** - Thread-safe design with generic type support
+* ๐ฆ **Ultra-Lightweight** - Only ~10KB JAR size with provided dependencies
+* ๐ **Microservices-Ready** - Built-in trace IDs for distributed tracing
+* โ
**Battle-Tested** - Used in production Spring Boot applications
+* ๐ **Professional-Grade Javadoc** - 100% coverage with comprehensive method documentation
+* ๐ **Opt-in Security Features** - Fine-grained control with field-level annotations
+* ๐ซ **Zero External Dependencies** - Pure Java, no Lombok required
## โจ Features
-- ๐ฏ **Consistent Structure** - All responses follow the same format: `status`, `message`, `content`, `timestamp`
-- ๐ **Type-Safe** - Full generic type support with compile-time type checking
-- ๐ **Distributed Tracing** - Trace IDs in error responses with MDC integration for request tracking
-- โฐ **Auto Timestamps** - Automatic RFC 3339 UTC formatted timestamps on every response
-- ๐ญ **Factory Methods** - Clean static methods: `success()`, `created()`, `status()`
-- ๐ **Zero Config** - Spring Boot Auto-Configuration for instant setup
-- ๐ชถ **Lightweight** - Only ~10KB JAR with single provided dependency (Spring Web)
-- ๐ฆ **Immutable** - Thread-safe with final fields
-- ๐ **Spring Native** - Built on `ResponseEntity` and `HttpStatus`
-- ๐ **RFC 9457 Compliance** - Standard ProblemDetail format (supersedes RFC 7807)
-- ๐ **Complete JavaDoc** - Every class and method fully documented with comprehensive examples
-- ๐ **Opt-in Security Features** - Fine-grained JSON request protection via field annotations
- - โ
**Strict JSON Validation** - Rejects unknown properties to prevent mass assignment attacks (automatic)
- - โ
**XSS Prevention** - HTML tag detection and rejection via `@XssCheck` annotation (opt-in)
- - โ
**Smart String Trimming** - Whitespace trimming via `@AutoTrim` annotation (opt-in)
- - โ
**Case-Insensitive Enums** - Flexible enum handling for better API usability (automatic)
-- ๐ก๏ธ **Comprehensive Exception Handling** - 10 built-in handlers covering all common scenarios
- - โ
Validation errors (`@Valid` annotations)
- - โ
Type mismatches (wrong parameter types)
- - โ
Malformed JSON (invalid request bodies)
- - โ
Missing parameters (required `@RequestParam`)
- - โ
404 Not Found (missing endpoints/resources)
- - โ
405 Method Not Allowed (wrong HTTP method)
- - โ
415 Unsupported Media Type (invalid Content-Type)
- - โ
Null pointer exceptions
- - โ
Custom business exceptions (`ApiException`)
- - โ
General unexpected errors
-- ๐ญ **Custom Business Exceptions** - Abstract `ApiException` class for domain-specific errors
-- โ
**Validation Support** - Automatic `@Valid` annotation error handling
+* ๐ฏ **Consistent Structure** - All responses follow the same format: `status`, `message`, `content`, `timestamp`
+* ๐ **@AutoResponse Wrapping** - Return plain DTOs; let the library wrap them automatically (Opt-in)
+* ๐ **Type-Safe** - Full generic type support with compile-time type checking
+* ๐ **Distributed Tracing** - Trace IDs in error responses with MDC integration for request tracking
+* โฐ **Auto Timestamps** - Automatic RFC 3339 UTC formatted timestamps on every response
+* ๐ญ **Factory Methods** - Clean static methods: `success()`, `created()`, `status()`
+* ๐ **Zero Config** - Spring Boot Auto-Configuration for instant setup
+* ๐ชถ **Lightweight** - Only ~10KB JAR with single provided dependency (Spring Web)
+* ๐ฆ **Immutable** - Thread-safe with final fields
+* ๐ **Spring Native** - Built on `ResponseEntity` and `HttpStatus`
+* ๐ **RFC 9457 Compliance** - Standard ProblemDetail format (supersedes RFC 7807)
+* ๐ **Complete JavaDoc** - Every class and method fully documented with comprehensive examples
+* ๐ **Opt-in Security Features** - Fine-grained JSON request protection via field annotations
+* โ
**Strict JSON Validation** - Rejects unknown properties to prevent mass assignment attacks (automatic)
+* โ
**XSS Prevention** - HTML tag detection and rejection via `@XssCheck` annotation (opt-in)
+* โ
**Smart String Trimming** - Whitespace trimming via `@AutoTrim` annotation (opt-in)
+* โ
**Case-Insensitive Enums** - Flexible enum handling for better API usability (automatic)
+* ๐ก๏ธ **Comprehensive Exception Handling** - 10 built-in handlers covering all common scenarios
## ๐ฆ Requirements
-- Java 17 or higher
-- Spring Boot 3.2.0 or higher (tested up to 4.0.3)
-- No additional dependencies required (pure Java implementation)
+* Java 17 or higher
+* Spring Boot 3.2.0 or higher (tested up to 4.0.3)
+* No additional dependencies required (pure Java implementation)
## ๐ What Makes This Different?
Unlike other response wrapper libraries, this one offers:
-- โ
**Native Spring Boot 3.x/4.x Auto-Configuration** - No manual setup required
-- โ
**RFC 9457 ProblemDetail Support** - Industry-standard error responses (latest RFC)
-- โ
**Opt-in Security Features** - Fine-grained control via field-level annotations
- - `@XssCheck` - HTML tag detection and rejection (fail-fast approach)
- - `@AutoTrim` - Whitespace trimming for specific fields
- - Regex-based validation: `(?s).*<\s*[a-zA-Z/!].*`
- - Default: Fields are NOT modified unless explicitly annotated
-- โ
**Zero External Dependencies** - Pure Java implementation, won't conflict with your application
-- โ
**Extensible Exception Handling** - Create custom business exceptions easily
-- โ
**Trace ID Support** - Built-in distributed tracing capabilities
-- โ
**Professional-Grade Documentation** - 100% Javadoc coverage with comprehensive method documentation including:
- - Detailed security feature explanations
- - Code examples for all major features
- - Complete @param, @return, @see, and @since tags
- - Method-level documentation explaining design decisions
- - Zero Javadoc warnings on build
-- โ
**Production-Grade Quality** - Clean builds, proper documentation, and battle-tested code
+* โ
**Native Spring Boot 3.x/4.x Auto-Configuration** - No manual setup required
+* โ
**Zero-Boilerplate @AutoResponse** - Return raw objects, let the library wrap them automatically while preserving
+ your HTTP Status codes.
+* โ
**RFC 9457 ProblemDetail Support** - Industry-standard error responses (latest RFC)
+* โ
**Opt-in Security Features** - Fine-grained control via field-level annotations (`@XssCheck`, `@AutoTrim`)
+* โ
**Zero External Dependencies** - Pure Java implementation, won't conflict with your application
+* โ
**Extensible Exception Handling** - Create custom business exceptions easily
+* โ
**Trace ID Support** - Built-in distributed tracing capabilities
+* โ
**Professional-Grade Documentation** - 100% Javadoc coverage
+
## ๐ Installation
-### Maven (Latest - v1.3.0)
+### Maven (Latest - v1.4.0)
```xml
+
io.github.og4dev
og4dev-spring-response
- 1.3.0
+ 1.4.0
+
```
-### Gradle (Latest - v1.3.0)
+### Gradle (Latest - v1.4.0)
```gradle
-implementation 'io.github.og4dev:og4dev-spring-response:1.3.0'
+implementation 'io.github.og4dev:og4dev-spring-response:1.4.0'
+
```
-### Gradle Kotlin DSL (Latest - v1.3.0)
+### Gradle Kotlin DSL (Latest - v1.4.0)
```kotlin
-implementation("io.github.og4dev:og4dev-spring-response:1.3.0")
+implementation("io.github.og4dev:og4dev-spring-response:1.4.0")
+
```
---
-
## ๐ Project Structure
-The library is organized into five main packages:
+The library is organized into six main packages:
```
io.github.og4dev
+โโโ advice/
+โ โโโ GlobalResponseWrapper.java # Automatic response wrapper interceptor
โโโ annotation/
+โ โโโ AutoResponse.java # Opt-in annotation for automatic wrapping
โ โโโ AutoTrim.java # Opt-in annotation for string trimming
โ โโโ XssCheck.java # Opt-in annotation for XSS validation
โโโ config/
@@ -194,498 +183,151 @@ io.github.og4dev
โ โโโ GlobalExceptionHandler.java # RFC 9457 exception handler
โโโ filter/
โโโ TraceIdFilter.java # Request trace ID generation
-```
-### Package Overview
-
-| Package | Description |
-|---------|-------------|
-| `annotation` | Field-level annotations for opt-in security and data processing features |
-| `config` | Spring Boot auto-configuration classes for zero-config setup |
-| `dto` | Data Transfer Objects - main `ApiResponse` wrapper class |
-| `exception` | Exception handling infrastructure with ProblemDetail support |
-| `filter` | Servlet filters for trace ID generation and MDC management |
+```
-### Key Components
+## ๐ฏ Quick Start
-- **ApiResponse** - Type-safe response wrapper with factory methods
-- **ApiResponseAutoConfiguration** - Automatic Spring Boot integration
-- **GlobalExceptionHandler** - Centralized exception handling with RFC 9457
-- **ApiException** - Base class for domain-specific exceptions
-- **@AutoTrim** - Annotation to opt-in to automatic string trimming for specific fields
-- **@XssCheck** - Annotation to opt-in to XSS validation for specific fields
-- **TraceIdFilter** - Automatic trace ID generation for distributed tracing
+You can use the library in two ways: **Explicit Factory Methods** or **Automatic Wrapping**.
-## ๐ฏ Quick Start
+### Method 1: Explicit Factory Methods
```java
-import io.github.og4dev.dto.ApiResponse;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
-
@GetMapping("/{id}")
public ResponseEntity> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return ApiResponse.success("User retrieved successfully", user);
}
}
-```
-**Response:**
-```json
-{
- "status": 200,
- "message": "User retrieved successfully",
- "content": {
- "id": 1,
- "name": "John Doe",
- "email": "john@example.com"
- },
- "timestamp": "2026-02-06T10:30:45.123Z"
-}
```
-## โ๏ธ Auto-Configuration
-
-The library now features **Spring Boot Auto-Configuration** for truly zero-config setup! Simply add the dependency and everything works automatically.
-
-### What Gets Auto-Configured
-
-When you include this library in your Spring Boot application, the following components are automatically registered:
+### Method 2: Automatic Wrapping (New in v1.4.0) ๐
-โ
**GlobalExceptionHandler** - Automatic exception handling with RFC 7807 ProblemDetail format
-โ
**Component Scanning** - All library components are automatically discovered
-โ
**Bean Registration** - No manual @ComponentScan or @Import required
+Tired of typing `ResponseEntity>`? Use `@AutoResponse`!
-### How It Works
-
-The library includes `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` which Spring Boot 3.x automatically detects and loads the `ApiResponseAutoConfiguration` class.
-
-**No configuration needed!** Just add the dependency:
-
-```xml
-
- io.github.og4dev
- og4dev-spring-response
- 1.1.1
-
-```
+```java
-### Disabling Auto-Configuration (Optional)
+@RestController
+@RequestMapping("/api/users")
+@AutoResponse // Applies to all methods in this controller
+public class UserController {
-If you need to customize or disable the auto-configuration:
+ @GetMapping("/{id}")
+ public User getUser(@PathVariable Long id) {
+ // Just return the raw object!
+ return userService.findById(id);
+ }
-```java
-@SpringBootApplication(exclude = ApiResponseAutoConfiguration.class)
-public class Application {
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED) // Preserves custom status codes!
+ public User createUser(@RequestBody UserDto dto) {
+ return userService.create(dto);
}
}
-```
-Or in `application.properties`:
-```properties
-spring.autoconfigure.exclude=io.github.og4dev.config.ApiResponseAutoConfiguration
```
-## ๐ Built-in Security Features
-
-The library provides fine-grained security and data processing features through field-level annotations. By default, **fields are NOT modified** unless explicitly annotated, giving you complete control over which fields receive special treatment.
-
-### Security Philosophy: Opt-in by Default
-
-**Version 1.3.0** introduces an opt-in approach:
-- โ
**Default Behavior**: String fields are preserved as-is (no trimming, no XSS validation)
-- โ
**Explicit Control**: Use `@AutoTrim` and `@XssCheck` annotations to enable features per field
-- โ
**No Surprises**: Your data is not modified unless you explicitly request it
-- โ
**Production-Ready**: Enable security only where needed
-
-### 1. Strict Property Validation ๐ก๏ธ (Automatic)
-
-**Prevents mass assignment vulnerabilities and data injection attacks**
-
-Automatically rejects JSON payloads containing unexpected fields:
-
-```java
-// Your DTO
-public class UserDto {
- private String name;
- private String email;
- // getters and setters
-}
-
-// โ
Valid request
-{
- "name": "John Doe",
- "email": "john@example.com"
-}
+**Both methods produce the exact same JSON:**
-// โ Rejected with 400 Bad Request
+```json
{
- "name": "John Doe",
- "email": "john@example.com",
- "isAdmin": true // Unknown field - potential mass assignment attack
+ "status": 200,
+ // or 201 for POST
+ "message": "Success",
+ "content": {
+ "id": 1,
+ "name": "John Doe"
+ },
+ "timestamp": "2026-02-28T10:30:45.123Z"
}
-```
-
-**Benefits:**
-- Prevents privilege escalation attempts
-- Catches client-side typos early
-- Enforces strict API contracts
-- Improves API security posture
-
-### 2. Opt-in XSS Prevention with @XssCheck ๐
-
-**Fail-fast HTML tag detection and rejection (opt-in per field)**
-Use the `@XssCheck` annotation to enable XSS validation on specific string fields:
-
-```java
-import io.github.og4dev.annotation.XssCheck;
-
-public class CommentDTO {
- @XssCheck
- private String content; // XSS validated
-
- @XssCheck
- private String authorName; // XSS validated
-
- private String commentId; // NOT validated (no annotation)
-}
```
-**What happens:**
+## โ๏ธ Auto-Configuration
-```java
-// โ
Valid requests (accepted for @XssCheck fields)
-{
- "content": "Hello World", // Plain text
- "authorName": "John Doe", // Plain text
- "commentId": "" // No validation (no @XssCheck)
-}
+The library features **Spring Boot Auto-Configuration** for truly zero-config setup!
-{
- "content": "Price: $100 < $200", // Comparison operators OK
- "authorName": "User@2024" // Special characters OK
-}
+โ
**GlobalExceptionHandler** - Automatic exception handling
-// โ Invalid requests (rejected with 400 Bad Request for @XssCheck fields)
-{
- "content": "", // Script injection
- "authorName": "John ", // HTML tags
- "commentId": "" // NOT rejected (no @XssCheck)
-}
-```
+โ
**GlobalResponseWrapper** - Automatic payload wrapping via `@AutoResponse`
-**Error Response:**
-```json
-{
- "type": "about:blank",
- "title": "Bad Request",
- "status": 400,
- "detail": "Security Error: HTML tags or XSS payloads are not allowed in the request.",
- "traceId": "550e8400-e29b-41d4-a716-446655440000",
- "timestamp": "2026-02-21T10:30:45.123Z"
-}
-```
+โ
**Security Customizers** - Jackson configuration for `@AutoTrim` and `@XssCheck`
-**Features:**
-- โ
Detects HTML tags using regex: `(?s).*<\s*[a-zA-Z/!].*`
-- โ
Rejects requests entirely (fail-fast approach)
-- โ
Works only on annotated fields
-- โ
Preserves null values (doesn't convert to empty strings)
-- โ
Supports multiline content (DOTALL mode)
+**No configuration needed!** Just add the dependency.
-**Security Advantage:**
-Unlike HTML escaping (which transforms `<` to `<`), this fail-fast approach **rejects the request entirely**. This prevents:
-- Stored XSS attacks
-- DOM-based XSS attacks
-- Second-order injection vulnerabilities
-- Encoding bypass attempts
+## ๐ Opt-in Automatic Wrapping (@AutoResponse)
-### 3. Opt-in String Trimming with @AutoTrim โ๏ธ
+Introduced in **v1.4.0**, you can eliminate boilerplate code by letting the library wrap your controller responses
+automatically.
-**Whitespace removal for specific fields (opt-in per field)**
+### How to use it:
-Use the `@AutoTrim` annotation to enable automatic whitespace trimming on specific string fields:
+Add the `@AutoResponse` annotation to your controller class (applies to all methods) or to specific methods.
```java
-import io.github.og4dev.annotation.AutoTrim;
-
-public class UserRegistrationDTO {
- @AutoTrim
- private String username; // Trimmed: " john_doe " โ "john_doe"
-
- @AutoTrim
- private String email; // Trimmed: " user@example.com " โ "user@example.com"
-
- private String password; // NOT trimmed: " pass123 " โ " pass123 "
- private String bio; // NOT trimmed: preserves formatting
-}
-```
-**What happens:**
-
-```java
-// Input JSON
-{
- "username": " john_doe ",
- "email": " user@example.com ",
- "password": " myPass123 ",
- "bio": " Software Developer "
+@RestController
+@AutoResponse
+public class ProductController {
+ // ...
}
-// After Deserialization
-username = "john_doe" // โ Trimmed (has @AutoTrim)
-email = "user@example.com" // โ Trimmed (has @AutoTrim)
-password = " myPass123 " // โ NOT trimmed (no annotation)
-bio = " Software Developer " // โ NOT trimmed (no annotation)
```
-**Use Cases for @AutoTrim:**
-- User input fields: names, emails, addresses where whitespace is unwanted
-- Search queries: remove accidental spaces from user inputs
-- Usernames: ensure consistent formatting without extra spaces
-- Reference numbers: IDs, codes that should not have whitespace
-- Categories/Tags: taxonomy values that need consistent formatting
-
-**When NOT to use @AutoTrim:**
-- Password fields where whitespace may be intentional
-- Code snippets or formatted text requiring exact spacing
-- Base64-encoded data that should not be modified
-- Poetry, ASCII art, or pre-formatted documents
-- API keys/tokens that must be processed exactly as provided
-
-### 4. Combining Annotations for Maximum Security
+### Key Capabilities:
-You can use both `@AutoTrim` and `@XssCheck` together for fields that need both behaviors:
-
-```java
-import io.github.og4dev.annotation.AutoTrim;
-import io.github.og4dev.annotation.XssCheck;
-
-public class SecureInputDTO {
- @AutoTrim
- @XssCheck
- private String username; // First trimmed, then XSS-validated
-
- @XssCheck
- private String comment; // Only XSS-validated (not trimmed)
-
- @AutoTrim
- private String email; // Only trimmed (not XSS-validated)
-
- private String bio; // Neither (preserved as-is)
-}
-```
+* โ
**Status Code Preservation:** Intelligently reads custom HTTP status codes set via `@ResponseStatus` (e.g., 201
+ Created) and reflects them in the `ApiResponse`.
+* โ
**Double-Wrap Prevention:** Safely skips wrapping if you explicitly return an `ApiResponse` or `ResponseEntity`.
+* โ
**Error Compatibility:** Bypasses `ProblemDetail` responses, ensuring standard error handling is never broken.
+* โ
**String Safety:** Skips raw `String` returns to prevent `ClassCastException` with Spring's internal string message
+ converters.
-**Processing order:**
-1. String is trimmed (if `@AutoTrim` is present)
-2. Trimmed string is checked for HTML tags (if `@XssCheck` is present)
-3. If HTML tags found, `IllegalArgumentException` is thrown
+## ๐ Built-in Security Features
-**Example:**
+The library provides fine-grained security and data processing features through field-level annotations. By default, **fields are NOT modified** unless explicitly annotated.
-```java
-// These will be rejected (after trimming):
-{"username": " "} // โ XSS attempt blocked
-{"username": " "} // โ HTML tag blocked
-
-// These are accepted:
-{"username": " john_doe "} // โ Trimmed to "john_doe"
-{"comment": " 5 < 10"} // โ Rejected (has HTML tag)
-{"email": " user@example.com "} // โ Trimmed to "user@example.com"
-{"bio": " "} // โ Preserved as-is (no annotations)
-```
+### 1. Strict Property Validation ๐ก๏ธ (Automatic)
-### 5. Case-Insensitive Enum Handling ๐ฏ (Automatic)
+Rejects JSON payloads containing unexpected fields to prevent mass assignment attacks.
-**Better API usability without compromising type safety**
+### 2. Opt-in XSS Prevention with @XssCheck ๐
-Allows flexible enum value formats from clients:
+Fail-fast HTML tag detection and rejection using regex pattern `(?s).*<\s*[a-zA-Z/!].*`.
```java
-public enum Status {
- ACTIVE, INACTIVE, PENDING
-}
-
-// All of these work โ
-{ "status": "ACTIVE" }
-{ "status": "active" }
-{ "status": "Active" }
-{ "status": "AcTiVe" }
-```
-### How It Works
+@XssCheck
+private String comment; // Rejects ""
-These features are automatically configured via the `strictJsonCustomizer()` method in `ApiResponseAutoConfiguration`:
-
-```java
-@Bean
-public JsonMapperBuilderCustomizer strictJsonCustomizer() {
- return builder -> {
- // 1. Reject unknown properties (automatic)
- builder.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
-
- // 2. Allow case-insensitive enums (automatic)
- builder.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
-
-
- // 3. Register AdvancedStringDeserializer for opt-in features
- SimpleModule stringModule = new SimpleModule();
-
- class AdvancedStringDeserializer extends StdScalarDeserializer {
- private final boolean shouldTrim;
- private final boolean shouldXssCheck;
-
- public AdvancedStringDeserializer() {
- super(String.class);
- this.shouldTrim = false; // Default: NO trimming
- this.shouldXssCheck = false; // Default: NO XSS checking
- }
-
- public AdvancedStringDeserializer(boolean shouldTrim, boolean shouldXssCheck) {
- super(String.class);
- this.shouldTrim = shouldTrim;
- this.shouldXssCheck = shouldXssCheck;
- }
-
- @Override
- public String deserialize(JsonParser p, DeserializationContext ctxt) {
- String value = p.getValueAsString();
- if (value == null) return null;
-
- // Apply trimming only if @AutoTrim is present
- String processedValue = shouldTrim ? value.trim() : value;
-
- // Validate for HTML tags only if @XssCheck is present
- if (shouldXssCheck && processedValue.matches("(?s).*<\\s*[a-zA-Z/!].*")) {
- throw new IllegalArgumentException(
- "Security Error: HTML tags or XSS payloads are not allowed in the request."
- );
- }
-
- return processedValue;
- }
-
- @Override
- public ValueDeserializer> createContextual(
- DeserializationContext ct, BeanProperty property) {
- // Check for @AutoTrim and @XssCheck annotations on field
- if (property != null) {
- boolean trim = property.getAnnotation(AutoTrim.class) != null;
- boolean xss = property.getAnnotation(XssCheck.class) != null;
- return new AdvancedStringDeserializer(trim, xss);
- }
- return this;
- }
- }
-
- stringModule.addDeserializer(String.class, new AdvancedStringDeserializer());
- builder.addModules(stringModule);
- };
-}
```
-**Key Features:**
-- **Opt-in by Default:** Fields are NOT modified unless explicitly annotated
-- **Fine-grained Control:** Each field can have different behavior via annotations
-- **Context-Aware:** Uses `createContextual()` to detect field annotations
-- **Four Modes:**
- - No annotations: String preserved as-is
- - `@AutoTrim` only: String is trimmed
- - `@XssCheck` only: String is XSS-validated
- - Both annotations: String is trimmed then XSS-validated
-- **Performance:** Deserializer created once per field at initialization, not per request
-
-### Disabling Security Features (Not Recommended)
+### 3. Opt-in String Trimming with @AutoTrim โ๏ธ
-If you need to disable these features for specific use cases:
+Automatic whitespace removal for specific fields.
```java
-@Configuration
-public class CustomJacksonConfig {
-
- @Bean
- @Primary // Takes precedence over library's bean
- public JsonMapperBuilderCustomizer myCustomizer() {
- return builder -> {
- // Your custom configuration
- builder.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
- };
- }
-}
-```
-Or disable the entire auto-configuration:
+@AutoTrim
+private String username; // " john_doe " -> "john_doe"
-```properties
-spring.autoconfigure.exclude=io.github.og4dev.config.ApiResponseAutoConfiguration
```
-### Documentation
-
-For complete technical details, see the comprehensive Javadoc documentation:
-- ๐ [ApiResponseAutoConfiguration.strictJsonCustomizer()](https://javadoc.io/doc/io.github.og4dev/og4dev-spring-response/latest/io/github/og4dev/config/ApiResponseAutoConfiguration.html#strictJsonCustomizer())
+*(See full Security details in the Javadocs and examples above).*
## ๐ก๏ธ Built-in Exception Handling
-The library includes a **production-ready `GlobalExceptionHandler`** that automatically handles common exceptions using Spring Boot's **ProblemDetail (RFC 9457)** standard.
-
-### What's Included
-
-โ
**General Exception Handler** - Catches all unhandled exceptions (HTTP 500)
-โ
**Validation Error Handler** - Automatically processes `@Valid` annotation failures (HTTP 400)
-โ
**Type Mismatch Handler** - Handles method argument type conversion errors (HTTP 400)
-โ
**Malformed JSON Handler** - Handles invalid JSON request bodies (HTTP 400)
-โ
**Missing Parameter Handler** - Detects missing required request parameters (HTTP 400)
-โ
**404 Not Found Handler** - Handles missing endpoints and resources (HTTP 404)
-โ
**Method Not Allowed Handler** - Handles unsupported HTTP methods (HTTP 405)
-โ
**Unsupported Media Type Handler** - Handles invalid Content-Type headers (HTTP 415)
-โ
**Null Pointer Handler** - Specific handling for NullPointerException (HTTP 500)
-โ
**Custom ApiException Handler** - Handles custom business exceptions extending `ApiException`
-โ
**Automatic Logging** - SLF4J integration for all errors with consistent trace IDs
-โ
**Trace ID Consistency** - Logs and responses always have matching trace IDs
-โ
**Timestamp Support** - All error responses include RFC 3339 timestamps
-โ
**RFC 9457 Compliance** - Standard ProblemDetail format (supersedes RFC 7807)
-
-### Trace ID Consistency
-
-All exception handlers now ensure **consistent trace IDs** between logs and error responses:
-
-- **With TraceIdFilter**: Uses the trace ID from SLF4J MDC
-- **Without Filter**: Generates a UUID and stores it in MDC for consistent logging
-- **Guaranteed**: Logs and responses always have matching trace IDs
-
-**Example Log Output:**
-```
-2026-02-07 10:30:45.123 [550e8400-e29b-41d4-a716-446655440000] ERROR GlobalExceptionHandler - Error in SQLExceptionTranslator:112
-```
-
-**Matching Error Response:**
-```json
-{
- "type": "about:blank",
- "title": "Internal Server Error",
- "status": 500,
- "detail": "Internal Server Error. Please contact technical support",
- "traceId": "550e8400-e29b-41d4-a716-446655440000",
- "timestamp": "2026-02-07T10:30:45.123Z"
-}
-```
-
-โ
**No more "N/A" trace IDs** - Every error has a real, correlatable trace ID
-โ
**Easy debugging** - Copy trace ID from response and find all related logs
-โ
**Thread-safe** - Proper MDC management ensures no cross-thread contamination
-
-### Custom Business Exceptions
+The library includes a **production-ready `GlobalExceptionHandler**` that automatically handles 10 common exceptions
+using Spring Boot's **ProblemDetail (RFC 9457)** standard.
-Instead of throwing generic exceptions, you can now extend the **abstract `ApiException` class** to create domain-specific exceptions with automatic exception handling:
+* **Automatic Logging:** SLF4J integration for all errors.
+* **Trace ID Consistency:** Logs and responses always have matching trace IDs.
+* **Custom Business Exceptions:** Extend the abstract `ApiException` class to create domain-specific exceptions.
```java
public class ResourceNotFoundException extends ApiException {
@@ -694,2955 +336,106 @@ public class ResourceNotFoundException extends ApiException {
}
}
-public class UnauthorizedAccessException extends ApiException {
- public UnauthorizedAccessException(String message) {
- super(message, HttpStatus.UNAUTHORIZED);
- }
-}
-
-public class BusinessRuleViolationException extends ApiException {
- public BusinessRuleViolationException(String message) {
- super(message, HttpStatus.CONFLICT);
- }
-}
-```
-
-**Usage in Controllers:**
-
-```java
-@GetMapping("/{id}")
-public ResponseEntity> getUser(@PathVariable Long id) {
- User user = userService.findById(id)
- .orElseThrow(() -> new ResourceNotFoundException("User", id));
- return ApiResponse.success("User retrieved successfully", user);
-}
-```
-
-**Automatic Error Response (RFC 9457):**
-```json
-{
- "type": "about:blank",
- "title": "Not Found",
- "status": 404,
- "detail": "User not found with ID: 123",
- "traceId": "550e8400-e29b-41d4-a716-446655440000",
- "timestamp": "2026-02-02T10:30:45.123Z"
-}
-```
-
-The `GlobalExceptionHandler` automatically:
-- Extracts the HTTP status from your custom exception
-- Formats it as a ProblemDetail response
-- Logs the error with appropriate severity
-- Includes timestamps for traceability
-
-### Example: Validation Errors
-
-Add validation to your DTOs:
-
-```java
-public class UserDto {
- @NotBlank(message = "Name is required")
- private String name;
-
- @Email(message = "Email must be valid")
- @NotBlank(message = "Email is required")
- private String email;
-
- @Min(value = 18, message = "Age must be at least 18")
- private Integer age;
-}
-```
-
-Use `@Valid` in your controller:
-
-```java
-@PostMapping
-public ResponseEntity> createUser(@Valid @RequestBody UserDto dto) {
- User newUser = userService.create(dto);
- return ApiResponse.created("User created successfully", newUser);
-}
-```
-
-**Automatic Error Response:**
-```json
-{
- "type": "about:blank",
- "title": "Bad Request",
- "status": 400,
- "detail": "Validation Failed",
- "errors": {
- "email": "Email must be valid",
- "name": "Name is required",
- "age": "Age must be at least 18"
- },
- "traceId": "550e8400-e29b-41d4-a716-446655440000",
- "timestamp": "2026-02-02T10:30:45.123Z"
-}
-```
-
-### Example: Malformed JSON Errors **
-
-When a client sends invalid JSON (missing quotes, commas, etc.):
-
-```bash
-# Request with malformed JSON
-POST /api/users
-Content-Type: application/json
-
-{
- "name": "John Doe"
- "email": "invalid" # Missing comma
-}
```
-**Automatic Error Response:**
-```json
-{
- "type": "about:blank",
- "title": "Bad Request",
- "status": 400,
- "detail": "Malformed JSON request. Please check your request body format.",
- "traceId": "550e8400-e29b-41d4-a716-446655440000",
- "timestamp": "2026-02-02T10:30:45.123Z"
-}
-```
-
-### Example: Missing Required Parameters **
+## ๐ Real-World Examples
-When a required `@RequestParam` is missing:
+### Example 1: Clean CRUD Controller (Using @AutoResponse)
```java
-@GetMapping("/search")
-public ResponseEntity>> search(
- @RequestParam(required = true) String query) {
- // ...
-}
-```
-
-**Automatic Error Response:**
-```json
-{
- "type": "about:blank",
- "title": "Bad Request",
- "status": 400,
- "detail": "Required request parameter 'query' (type: String) is missing.",
- "traceId": "550e8400-e29b-41d4-a716-446655440000",
- "timestamp": "2026-02-02T10:30:45.123Z"
-}
-```
-
-### Example: 404 Not Found **
-
-When accessing a non-existent endpoint or resource:
-
-```bash
-GET /api/nonexistent
-```
-
-**Automatic Error Response:**
-```json
-{
- "type": "about:blank",
- "title": "Not Found",
- "status": 404,
- "detail": "The requested resource '/api/nonexistent' was not found.",
- "traceId": "550e8400-e29b-41d4-a716-446655440000",
- "timestamp": "2026-02-02T10:30:45.123Z"
-}
-```
-
-### Example: Method Not Allowed **
-When using an unsupported HTTP method:
-
-```bash
-# POST to an endpoint that only supports GET
-POST /api/users/123
-```
-
-**Automatic Error Response:**
-```json
-{
- "type": "about:blank",
- "title": "Method Not Allowed",
- "status": 405,
- "detail": "Method 'POST' is not supported for this endpoint. Supported methods are: [GET, PUT, DELETE]",
- "traceId": "550e8400-e29b-41d4-a716-446655440000",
- "timestamp": "2026-02-02T10:30:45.123Z"
-}
-```
+@RestController
+@RequestMapping("/api/products")
+@RequiredArgsConstructor
+@AutoResponse // โจ Zero boilerplate for the whole controller!
+public class ProductController {
-### Example: Unsupported Media Type **
+ private final ProductService productService;
-When sending an unsupported Content-Type:
+ @GetMapping
+ public Page getAllProducts(Pageable pageable) {
+ return productService.findAll(pageable);
+ }
-```bash
-POST /api/users
-Content-Type: application/xml
+ @GetMapping("/{id}")
+ public Product getProduct(@PathVariable Long id) {
+ return productService.findById(id)
+ .orElseThrow(() -> new ResourceNotFoundException("Product", id));
+ }
-John
-```
+ @PostMapping
+ @ResponseStatus(HttpStatus.CREATED) // โจ 201 Created preserved automatically
+ public Product createProduct(@Valid @RequestBody ProductDto dto) {
+ return productService.create(dto);
+ }
-**Automatic Error Response:**
-```json
-{
- "type": "about:blank",
- "title": "Unsupported Media Type",
- "status": 415,
- "detail": "Content type 'application/xml' is not supported. Supported content types: [application/json]",
- "traceId": "550e8400-e29b-41d4-a716-446655440000",
- "timestamp": "2026-02-02T10:30:45.123Z"
+ @DeleteMapping("/{id}")
+ public void deleteProduct(@PathVariable Long id) {
+ productService.delete(id);
+ // Returns empty content with 200 OK automatically
+ }
}
-```
-
-### Logging
-
-All exceptions are automatically logged with appropriate severity levels:
-- **ERROR level** - General exceptions and null pointer exceptions
-- **WARN level** - Validation errors, type mismatches, malformed JSON, missing parameters, 404 errors, method not allowed, unsupported media types, and business logic exceptions
```
-2026-02-02 10:30:45.123 WARN i.g.p.e.GlobalExceptionHandler - Validation error: {email=Email must be valid, name=Name is required}
-2026-02-02 10:30:45.456 ERROR i.g.p.e.GlobalExceptionHandler - An unexpected error occurred:
-java.lang.RuntimeException: Database connection failed
- at com.example.service.UserService.findById(UserService.java:42)
-2026-02-02 10:31:12.789 WARN i.g.p.e.GlobalExceptionHandler - Malformed JSON request: JSON parse error: Unexpected character...
-2026-02-02 10:31:45.234 WARN i.g.p.e.GlobalExceptionHandler - Missing parameter: Required request parameter 'query' (type: String) is missing.
-2026-02-02 10:32:15.567 WARN i.g.p.e.GlobalExceptionHandler - 404 Not Found: The requested resource '/api/invalid' was not found.
-2026-02-02 10:32:45.890 WARN i.g.p.e.GlobalExceptionHandler - Method not allowed: Method 'POST' is not supported for this endpoint.
-2026-02-02 10:33:15.123 WARN i.g.p.e.GlobalExceptionHandler - Unsupported media type: Content type 'application/xml' is not supported.
-```
-```
-
-## ๐ Distributed Tracing
-
-The library provides **production-ready distributed tracing** with automatic trace ID generation and MDC (Mapped Diagnostic Context) integration for seamless log correlation across your microservices architecture.
-### Key Features
-
-โ
**Automatic Trace ID Generation** - Every response includes a unique UUID for request tracking
-โ
**MDC Integration** - Trace IDs automatically available in all log statements via SLF4J MDC
-โ
**TraceIdFilter** - Optional servlet filter for consistent trace ID management **
-โ
**Flexible Priority** - Supports explicit trace IDs, MDC context, or auto-generation
-โ
**Thread-Safe** - Proper MDC cleanup prevents memory leaks
-โ
**Zero Configuration** - Works out of the box with sensible defaults
-โ
**UUID Format** - Standard 128-bit globally unique identifiers
-
-### Industry Standards Compatibility
-
-The trace ID implementation uses **UUID format** (128-bit), which is:
-- โ
Widely supported across distributed systems
-- โ
Compatible with most logging and APM tools
-- โ
Globally unique without coordination
+## ๐ API Reference
-**For enhanced interoperability**, consider implementing header propagation to support:
-- `X-Trace-Id` / `X-Request-ID` (Common custom headers)
-- `X-B3-TraceId` (Zipkin/B3 format)
-- `traceparent` (W3C Trace Context standard)
+*(Refer to Javadoc for full details)*
-See the [Enhanced TraceIdFilter](#enhanced-traceidfilter-with-header-propagation) section below for implementation details.
+## ๐ Version History
-### How It Works
+### 1.4.0 (February 2026) - Current Release
-#### Default Behavior (Without Filter)
+โจ **New Features:**
-By default, `ApiResponse` automatically generates a trace ID for each response:
+* **@AutoResponse Annotation & GlobalResponseWrapper**
+* Opt-in automatic response wrapping to eliminate boilerplate code.
+* Returns raw DTOs from controllers and automatically wraps them in `ApiResponse`.
+* Preserves HTTP status codes from `@ResponseStatus`.
+* Intelligently skips `ResponseEntity`, `ApiResponse`, `ProblemDetail`, and `String` to prevent double-wrapping and
+ casting errors.
-```java
-@GetMapping("/{id}")
-public ResponseEntity> getUser(@PathVariable Long id) {
- User user = userService.findById(id);
- return ApiResponse.success("User found", user);
-}
-```
-**Response:**
-```json
-{
- "status": 200,
- "message": "User found",
- "content": { "id": 1, "name": "John" },
- "timestamp": "2026-02-06T10:30:45.123Z"
-}
-```
+* **package-info.java documentation** added for the new `advice` package.
-**Note:** Success responses (ApiResponse) do NOT include `traceId`. Trace IDs are only included in error responses (ProblemDetail format) for debugging purposes.
-```
+### 1.3.0 (February 2026)
-#### Enhanced Tracing with TraceIdFilter
+* **Security Philosophy Change:** Complete redesign from automatic to opt-in approach for JSON sanitization.
+* Added **@AutoTrim** annotation for explicit string trimming.
+* Added **@XssCheck** annotation for explicit fail-fast XSS validation.
+* Extensive Javadoc and README updates regarding the new security model.
-For comprehensive distributed tracing, register the `TraceIdFilter` to automatically manage trace IDs across your entire request lifecycle:
+### 1.2.0 (February 2026)
-**Step 1: Register the Filter**
+* Added `@NoTrim` annotation (Legacy - removed in 1.3.0 in favor of opt-in model).
+* Enhanced Advanced String Deserializer.
-```java
-@Configuration
-public class FilterConfig {
-
- @Bean
- public FilterRegistrationBean traceIdFilter() {
- FilterRegistrationBean registration = new FilterRegistrationBean<>();
- registration.setFilter(new TraceIdFilter());
- registration.addUrlPatterns("/*");
- registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
- return registration;
- }
-}
-```
+### 1.1.0 & 1.1.1 (February 2026)
-**Step 2: Automatic Benefits**
+* Added `GlobalExceptionHandler` and RFC 9457 Support.
+* Added `TraceIdFilter` and MDC Integration.
+* Verified Spring Boot 4.0.3 Compatibility.
-Once registered, the filter:
-1. Generates a unique UUID for each incoming request
-2. Stores it in SLF4J MDC (available for logging)
-3. Makes it available to `ApiResponse` automatically
-4. Cleans up MDC after request completion
+### 1.0.0 (February 2026)
-**Step 3: Trace ID in Logs**
+* Initial Release. Core `ApiResponse` wrapper.
-All your log statements automatically include the trace ID:
+---
-```java
-@Service
-@Slf4j
-public class UserService {
-
- public User findById(Long id) {
- log.info("Finding user by ID: {}", id); // Trace ID automatically included
- // ... business logic
- log.debug("User retrieved from database");
- return user;
- }
-}
-```
+## ๐ค Contributing
-**Log Output (with Logback configuration):**
-```
-2026-02-06 10:30:45.123 [a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d] INFO c.e.s.UserService - Finding user by ID: 123
-2026-02-06 10:30:45.234 [a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d] DEBUG c.e.s.UserService - User retrieved from database
-```
+We welcome contributions! Please see the Contributing section above for details on our Apache 2.0 license terms and PR
+process.
-**Logback Configuration (logback-spring.xml):**
-```xml
-
-
-
- %d{yyyy-MM-DD HH:mm:ss.SSS} [%X{traceId}] %-5level %logger{36} - %msg%n
-
-
-
-
-
-
-
-```
+## ๐ License
-### Trace ID Priority
+Licensed under the Apache License 2.0.
-The trace ID is resolved with the following priority:
+## ๐ง Contact
-1. **Explicit Parameter** - If you explicitly set a trace ID via builder
-2. **MDC Context** - If `TraceIdFilter` has set a trace ID in MDC
-3. **Auto-Generated** - Falls back to a random UUID
-
-```java
-// Priority 1: Explicit trace ID
-UUID customId = UUID.randomUUID();
-ApiResponse.builder()
- .traceId(customId) // This takes highest priority
- .status(200)
- .message("User found")
- .content(user)
- .build();
-
-// Priority 2: From MDC (when TraceIdFilter is active)
-// Automatically uses the filter-generated trace ID
-
-// Priority 3: Auto-generated (when no filter and no explicit ID)
-// Falls back to random UUID
-```
-
-### End-to-End Tracing Example
-
-**Scenario:** Client โ API Gateway โ User Service โ Database
-
-```java
-// API Gateway receives request with trace ID
-@RestController
-@RequestMapping("/api/users")
-public class UserController {
-
- @GetMapping("/{id}")
- public ResponseEntity> getUser(@PathVariable Long id) {
- // TraceIdFilter already set MDC with trace ID
- log.info("Received request for user: {}", id);
-
- User user = userService.findById(id);
-
- // Response automatically includes the same trace ID
- return ApiResponse.success("User found", user);
- }
-}
-
-@Service
-@Slf4j
-public class UserService {
-
- public User findById(Long id) {
- // All logs automatically include trace ID from MDC
- log.info("Finding user in database: {}", id);
-
- User user = userRepository.findById(id)
- .orElseThrow(() -> {
- log.warn("User not found: {}", id);
- return new ResourceNotFoundException("User", id);
- });
-
- log.info("User found: {}", user.getEmail());
- return user;
- }
-}
-```
-
-**Complete Request Flow:**
-```
-1. Request arrives โ TraceIdFilter generates UUID: a1b2c3d4-...
-2. MDC.put("traceId", "a1b2c3d4-...")
-3. Controller logs: [a1b2c3d4-...] INFO - Received request for user: 123
-4. Service logs: [a1b2c3d4-...] INFO - Finding user in database: 123
-5. Service logs: [a1b2c3d4-...] INFO - User found: john@example.com
-6. Response includes: "traceId": "a1b2c3d4-..."
-7. MDC.clear() in finally block
-```
-
-### Propagating Trace IDs to Downstream Services
-
-For microservices architectures, propagate the trace ID to downstream services:
-
-```java
-@Service
-@RequiredArgsConstructor
-public class OrderService {
-
- private final RestTemplate restTemplate;
-
- public Order createOrder(OrderDto dto) {
- // Get trace ID from MDC
- String traceId = MDC.get("traceId");
-
- // Create headers with trace ID
- HttpHeaders headers = new HttpHeaders();
- headers.set("X-Trace-Id", traceId);
- headers.setContentType(MediaType.APPLICATION_JSON);
-
- // Call downstream service
- HttpEntity request = new HttpEntity<>(dto, headers);
- ResponseEntity> response = restTemplate.exchange(
- "http://payment-service/api/payments",
- HttpMethod.POST,
- request,
- new ParameterizedTypeReference>() {}
- );
-
- return response.getBody().getContent();
- }
-}
-```
-
-**Downstream Service (Payment Service):**
-```java
-@RestController
-@RequestMapping("/api/payments")
-public class PaymentController {
-
- @PostMapping
- public ResponseEntity> processPayment(
- @RequestHeader(value = "X-Trace-Id", required = false) String incomingTraceId,
- @RequestBody PaymentDto dto) {
-
- // Use incoming trace ID if provided
- if (incomingTraceId != null) {
- MDC.put("traceId", incomingTraceId);
- }
-
- Payment payment = paymentService.process(dto);
- return ApiResponse.created("Payment processed", payment);
- }
-}
-```
-
-### Testing with Trace IDs
-
-**Unit Tests:**
-```java
-@Test
-void testTraceIdInResponse() {
- // Set trace ID in MDC (simulating TraceIdFilter)
- String expectedTraceId = UUID.randomUUID().toString();
- MDC.put("traceId", expectedTraceId);
-
- try {
- ApiResponse response = ApiResponse.builder()
- .status(200)
- .message("Success")
- .content(user)
- .build();
-
- assertEquals(UUID.fromString(expectedTraceId), response.getTraceId());
- } finally {
- MDC.clear();
- }
-}
-```
-
-**Integration Tests:**
-```java
-@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
-class UserControllerIntegrationTest {
-
- @Autowired
- private TestRestTemplate restTemplate;
-
- @Test
- void testTraceIdInApiResponse() {
- ResponseEntity> response = restTemplate.exchange(
- "/api/users/1",
- HttpMethod.GET,
- null,
- new ParameterizedTypeReference>() {}
- );
-
- assertNotNull(response.getBody().getTraceId());
- // Trace ID should be in response
- }
-}
-```
-
-### Benefits
-
-1. **Simplified Debugging** - Track requests across multiple services with a single ID
-2. **Log Correlation** - All logs for a request share the same trace ID
-3. **Performance Monitoring** - Identify slow requests by trace ID
-4. **Error Investigation** - Quickly find all logs related to a failed request
-5. **Distributed Systems** - Essential for microservices architecture
-6. **Production Ready** - No memory leaks with automatic MDC cleanup
-
-### Enhanced TraceIdFilter with Header Propagation
-
-For better interoperability with industry-standard distributed tracing systems, you can extend `TraceIdFilter` to support incoming trace ID headers and add trace IDs to response headers:
-
-```java
-package io.github.og4dev.filter;
-
-import jakarta.servlet.FilterChain;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import org.slf4j.MDC;
-import org.springframework.web.filter.OncePerRequestFilter;
-
-import java.io.IOException;
-import java.util.UUID;
-
-/**
- * Enhanced TraceIdFilter with industry-standard header support.
- * Supports incoming trace IDs from common headers and propagates to response.
- */
-public class EnhancedTraceIdFilter extends OncePerRequestFilter {
-
- @Override
- protected void doFilterInternal(HttpServletRequest request,
- HttpServletResponse response,
- FilterChain filterChain) throws ServletException, IOException {
- UUID traceId = extractOrGenerateTraceId(request);
-
- try {
- request.setAttribute("traceId", traceId);
- MDC.put("traceId", traceId.toString());
-
- // Add trace ID to response header for downstream services
- response.setHeader("X-Trace-Id", traceId.toString());
-
- filterChain.doFilter(request, response);
- } finally {
- MDC.clear();
- }
- }
-
- /**
- * Extracts trace ID from standard headers or generates a new one.
- * Checks headers in order of priority:
- * 1. X-Trace-Id (custom)
- * 2. X-Request-ID (common)
- * 3. X-B3-TraceId (Zipkin)
- * 4. traceparent (W3C Trace Context)
- */
- private UUID extractOrGenerateTraceId(HttpServletRequest request) {
- // Check X-Trace-Id header
- String traceId = request.getHeader("X-Trace-Id");
-
- // Check X-Request-ID header
- if (traceId == null) {
- traceId = request.getHeader("X-Request-ID");
- }
-
- // Check X-B3-TraceId header (Zipkin format)
- if (traceId == null) {
- traceId = request.getHeader("X-B3-TraceId");
- }
-
- // Check W3C traceparent header
- if (traceId == null) {
- String traceparent = request.getHeader("traceparent");
- if (traceparent != null) {
- // Format: 00-{trace-id}-{span-id}-{flags}
- String[] parts = traceparent.split("-");
- if (parts.length >= 2) {
- traceId = parts[1]; // Extract trace-id
- }
- }
- }
-
- // Try to parse as UUID, or generate new one
- if (traceId != null) {
- try {
- return UUID.fromString(traceId);
- } catch (IllegalArgumentException e) {
- // Invalid UUID format, generate new one
- }
- }
-
- return UUID.randomUUID();
- }
-}
-```
-
-**Register the Enhanced Filter:**
-
-```java
-@Configuration
-public class FilterConfig {
-
- @Bean
- public FilterRegistrationBean traceIdFilter() {
- FilterRegistrationBean registration = new FilterRegistrationBean<>();
- registration.setFilter(new EnhancedTraceIdFilter());
- registration.addUrlPatterns("/*");
- registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
- return registration;
- }
-}
-```
-
-**Benefits of Enhanced Filter:**
-- โ
Accepts trace IDs from upstream services (API Gateway, Load Balancer)
-- โ
Supports multiple industry-standard header formats
-- โ
Adds trace ID to response headers for downstream services
-- โ
Maintains compatibility with W3C, Zipkin, and custom formats
-- โ
Graceful fallback to UUID generation
-
-**Testing with cURL:**
-```bash
-# Send request with trace ID in header
-curl -H "X-Trace-Id: 12345678-1234-1234-1234-123456789012" \
- http://localhost:8080/api/users/1
-
-# Check response header - trace ID is propagated
-HTTP/1.1 200 OK
-X-Trace-Id: 12345678-1234-1234-1234-123456789012
-
-# Success response body (no traceId field in ApiResponse)
-{
- "status": 200,
- "message": "User found",
- "content": {...},
- "timestamp": "2026-02-06T10:30:45.123Z"
-}
-
-# Error responses include traceId in body (ProblemDetail format)
-# For errors, the traceId appears in the response JSON:
-{
- "type": "about:blank",
- "title": "Not Found",
- "status": 404,
- "detail": "User not found",
- "traceId": "12345678-1234-1234-1234-123456789012",
- "timestamp": "2026-02-06T10:30:45.123Z"
-}
-```
-
-**Note:**
-- Success responses (ApiResponse) do NOT have `traceId` in the response body
-- Error responses (ProblemDetail) DO include `traceId` for debugging
-- Both success and error responses can have `X-Trace-Id` header if TraceIdFilter is configured
-
-## ๐ Usage
-
-### Success Responses (HTTP 200)
-
-**With data:**
-```java
-@GetMapping
-public ResponseEntity>> getAllUsers() {
- List users = userService.findAll();
- return ApiResponse.success("Users retrieved successfully", users);
-}
-```
-
-**Without data:**
-```java
-@DeleteMapping("/{id}")
-public ResponseEntity> deleteUser(@PathVariable Long id) {
- userService.delete(id);
- return ApiResponse.success("User deleted successfully");
-}
-```
-
-### Created Responses (HTTP 201)
-
-```java
-@PostMapping
-public ResponseEntity> createUser(@RequestBody UserDto dto) {
- User newUser = userService.create(dto);
- return ApiResponse.created("User created successfully", newUser);
-}
-```
-
-### Custom Status Responses
-
-**Without data:**
-```java
-@GetMapping("/health")
-public ResponseEntity> healthCheck() {
- if (!service.isHealthy()) {
- return ApiResponse.status("Service unavailable", HttpStatus.SERVICE_UNAVAILABLE);
- }
- return ApiResponse.success("Service is healthy");
-}
-```
-
-**With data:**
-```java
-@PutMapping("/{id}")
-public ResponseEntity> updateUser(
- @PathVariable Long id,
- @RequestBody UserDto dto) {
- User updated = userService.update(id, dto);
- return ApiResponse.status("User updated", updated, HttpStatus.OK);
-}
-```
-
-## ๐ Real-World Examples
-
-### Example 1: Complete CRUD Controller
-
-```java
-@RestController
-@RequestMapping("/api/products")
-@RequiredArgsConstructor
-@Slf4j
-public class ProductController {
-
- private final ProductService productService;
-
- @GetMapping
- public ResponseEntity>> getAllProducts(
- @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) {
- Page products = productService.findAll(pageable);
- return ApiResponse.success("Products retrieved successfully", products);
- }
-
- @GetMapping("/{id}")
- public ResponseEntity> getProduct(@PathVariable Long id) {
- Product product = productService.findById(id)
- .orElseThrow(() -> new ResourceNotFoundException("Product", id));
- return ApiResponse.success("Product found", product);
- }
-
- @PostMapping
- public ResponseEntity> createProduct(
- @Valid @RequestBody ProductDto dto) {
- Product product = productService.create(dto);
- log.info("Product created with ID: {}", product.getId());
- return ApiResponse.created("Product created successfully", product);
- }
-
- @PutMapping("/{id}")
- public ResponseEntity> updateProduct(
- @PathVariable Long id,
- @Valid @RequestBody ProductDto dto) {
- Product product = productService.update(id, dto);
- return ApiResponse.success("Product updated successfully", product);
- }
-
- @DeleteMapping("/{id}")
- public ResponseEntity> deleteProduct(@PathVariable Long id) {
- productService.delete(id);
- return ApiResponse.success("Product deleted successfully");
- }
-
- @PatchMapping("/{id}/status")
- public ResponseEntity> updateStatus(
- @PathVariable Long id,
- @RequestParam ProductStatus status) {
- Product product = productService.updateStatus(id, status);
- return ApiResponse.success("Product status updated", product);
- }
-}
-```
-
-### Example 2: File Upload with Progress
-
-```java
-@RestController
-@RequestMapping("/api/files")
-@RequiredArgsConstructor
-public class FileUploadController {
-
- private final FileStorageService fileService;
-
- @PostMapping("/upload")
- public ResponseEntity> uploadFile(
- @RequestParam("file") MultipartFile file) {
-
- if (file.isEmpty()) {
- throw new InvalidFileException("File cannot be empty");
- }
-
- if (file.getSize() > 10 * 1024 * 1024) { // 10MB limit
- throw new FileTooLargeException("File size exceeds 10MB limit");
- }
-
- FileMetadata metadata = fileService.store(file);
- return ApiResponse.created("File uploaded successfully", metadata);
- }
-
- @GetMapping("/{id}/download")
- public ResponseEntity downloadFile(@PathVariable String id) {
- FileData fileData = fileService.load(id);
-
- return ResponseEntity.ok()
- .contentType(MediaType.parseMediaType(fileData.getContentType()))
- .header(HttpHeaders.CONTENT_DISPOSITION,
- "attachment; filename=\"" + fileData.getFilename() + "\"")
- .body(fileData.getResource());
- }
-}
-
-// Custom exceptions
-public class InvalidFileException extends ApiException {
- public InvalidFileException(String message) {
- super(message, HttpStatus.BAD_REQUEST);
- }
-}
-
-public class FileTooLargeException extends ApiException {
- public FileTooLargeException(String message) {
- super(message, HttpStatus.PAYLOAD_TOO_LARGE);
- }
-}
-```
-
-### Example 3: Async Processing with Callbacks
-
-```java
-@RestController
-@RequestMapping("/api/reports")
-@RequiredArgsConstructor
-public class ReportController {
-
- private final ReportService reportService;
-
- @PostMapping("/generate")
- public ResponseEntity> generateReport(
- @Valid @RequestBody ReportRequest request) {
-
- ReportJob job = reportService.submitJob(request);
-
- return ApiResponse.status(
- "Report generation started. Check status at /api/reports/" + job.getId(),
- job,
- HttpStatus.ACCEPTED
- );
- }
-
- @GetMapping("/{jobId}/status")
- public ResponseEntity> getStatus(@PathVariable String jobId) {
- ReportJobStatus status = reportService.getJobStatus(jobId);
-
- return switch (status.getState()) {
- case COMPLETED -> ApiResponse.success("Report is ready", status);
- case FAILED -> ApiResponse.status("Report generation failed", status, HttpStatus.INTERNAL_SERVER_ERROR);
- case PROCESSING -> ApiResponse.status("Report is being generated", status, HttpStatus.ACCEPTED);
- default -> ApiResponse.status("Report is queued", status, HttpStatus.ACCEPTED);
- };
- }
-}
-```
-
-### Example 4: Search with Filters
-
-```java
-@RestController
-@RequestMapping("/api/search")
-@RequiredArgsConstructor
-public class SearchController {
-
- private final SearchService searchService;
-
- @GetMapping("/products")
- public ResponseEntity>> searchProducts(
- @RequestParam(required = false) String query,
- @RequestParam(required = false) BigDecimal minPrice,
- @RequestParam(required = false) BigDecimal maxPrice,
- @RequestParam(required = false) List categories,
- @RequestParam(required = false) Boolean inStock,
- Pageable pageable) {
-
- SearchCriteria criteria = SearchCriteria.builder()
- .query(query)
- .minPrice(minPrice)
- .maxPrice(maxPrice)
- .categories(categories)
- .inStock(inStock)
- .build();
-
- SearchResults results = searchService.search(criteria, pageable);
-
- String message = String.format("Found %d results", results.getTotalElements());
- return ApiResponse.success(message, results);
- }
-}
-```
-
-### Example 5: Batch Operations
-
-```java
-@RestController
-@RequestMapping("/api/batch")
-@RequiredArgsConstructor
-public class BatchOperationController {
-
- private final BatchService batchService;
-
- @PostMapping("/users/import")
- public ResponseEntity> importUsers(
- @RequestBody List<@Valid UserImportDto> users) {
-
- if (users.isEmpty()) {
- throw new InvalidRequestException("User list cannot be empty");
- }
-
- if (users.size() > 1000) {
- throw new BatchTooLargeException("Maximum 1000 users per batch");
- }
-
- BatchResult result = batchService.importUsers(users);
-
- String message = String.format(
- "Batch completed: %d successful, %d failed",
- result.getSuccessCount(),
- result.getFailureCount()
- );
-
- return ApiResponse.success(message, result);
- }
-
- @DeleteMapping("/users")
- public ResponseEntity> deleteUsers(
- @RequestBody List userIds) {
-
- BatchDeleteResult result = batchService.deleteUsers(userIds);
-
- return ApiResponse.success(
- String.format("Deleted %d users", result.getDeletedCount()),
- result
- );
- }
-}
-```
-
-### Example 6: Health Check & Monitoring
-
-```java
-@RestController
-@RequestMapping("/api")
-@RequiredArgsConstructor
-public class HealthController {
-
- private final DatabaseHealthChecker dbHealthChecker;
- private final CacheHealthChecker cacheHealthChecker;
- private final ExternalApiHealthChecker apiHealthChecker;
-
- @GetMapping("/health")
- public ResponseEntity> health() {
- HealthStatus status = HealthStatus.builder()
- .database(dbHealthChecker.check())
- .cache(cacheHealthChecker.check())
- .externalApi(apiHealthChecker.check())
- .timestamp(Instant.now())
- .build();
-
- HttpStatus httpStatus = status.isHealthy() ? HttpStatus.OK : HttpStatus.SERVICE_UNAVAILABLE;
- String message = status.isHealthy() ? "All systems operational" : "Some systems are down";
-
- return ApiResponse.status(message, status, httpStatus);
- }
-
- @GetMapping("/metrics")
- public ResponseEntity> metrics() {
- SystemMetrics metrics = SystemMetrics.builder()
- .uptime(ManagementFactory.getRuntimeMXBean().getUptime())
- .memoryUsage(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
- .activeThreads(Thread.activeCount())
- .timestamp(Instant.now())
- .build();
-
- return ApiResponse.success("System metrics retrieved", metrics);
- }
-}
-```
-
-### Error Handling with GlobalExceptionHandler
-
-**Version 2.0.0 includes a comprehensive `GlobalExceptionHandler`** with 10 built-in exception handlers using Spring Boot's `ProblemDetail` (RFC 9457) for standardized error responses:
-
-**Handled Exception Types:**
-1. โ
**General Exceptions** (HTTP 500)
-2. โ
**Validation Errors** (HTTP 400) - `@Valid` annotation failures
-3. โ
**Type Mismatches** (HTTP 400) - Wrong parameter types
-4. โ
**Malformed JSON** (HTTP 400) - Invalid request body **
-5. โ
**Missing Parameters** (HTTP 400) - Required `@RequestParam` missing **
-6. โ
**404 Not Found** (HTTP 404) - Missing endpoints/resources **
-7. โ
**405 Method Not Allowed** (HTTP 405) - Wrong HTTP method **
-8. โ
**415 Unsupported Media Type** (HTTP 415) - Invalid Content-Type **
-9. โ
**Null Pointer Exceptions** (HTTP 500)
-10. โ
**Custom ApiExceptions** - Domain-specific business logic errors
-
-```java
-package io.github.og4dev.exception;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ProblemDetail;
-import org.springframework.http.converter.HttpMessageNotReadableException;
-import org.springframework.validation.FieldError;
-import org.springframework.web.HttpMediaTypeNotSupportedException;
-import org.springframework.web.HttpRequestMethodNotSupportedException;
-import org.springframework.web.bind.MethodArgumentNotValidException;
-import org.springframework.web.bind.MissingServletRequestParameterException;
-import org.springframework.web.bind.annotation.ExceptionHandler;
-import org.springframework.web.bind.annotation.RestControllerAdvice;
-import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
-import org.springframework.web.servlet.resource.NoResourceFoundException;
-
-import java.time.Instant;
-import java.util.HashMap;
-import java.util.Map;
-
-@RestControllerAdvice
-public class GlobalExceptionHandler {
- private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
-
- // 10 comprehensive exception handlers included
- // See full implementation in the source code
-
- private String getOrGenerateTraceId() {
- String traceId = MDC.get("traceId");
- if (traceId == null) {
- traceId = UUID.randomUUID().toString();
- MDC.put("traceId", traceId);
- }
- return traceId;
- }
-
- @ExceptionHandler(Exception.class)
- public ProblemDetail handleAllExceptions(Exception ex) {
- String traceId = getOrGenerateTraceId();
- log.error("[TraceID: {}] An unexpected error occurred: ", traceId, ex);
- ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
- HttpStatus.INTERNAL_SERVER_ERROR,
- "Internal Server Error. Please contact technical support"
- );
- problemDetail.setProperty("traceId", traceId);
- problemDetail.setProperty("timestamp", Instant.now());
- return problemDetail;
- }
-
- @ExceptionHandler(MethodArgumentNotValidException.class)
- public ProblemDetail handleValidationExceptions(MethodArgumentNotValidException ex) {
- String traceId = getOrGenerateTraceId();
- Map errorMessage = new HashMap<>();
- for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
- errorMessage.merge(fieldError.getField(), fieldError.getDefaultMessage(),
- (msg1, msg2) -> msg1 + "; " + msg2);
- }
- log.warn("[TraceID: {}] Validation error: {}", traceId, errorMessage);
- ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
- HttpStatus.BAD_REQUEST,
- "Validation Failed"
- );
- problemDetail.setProperty("errors", errorMessage);
- problemDetail.setProperty("traceId", traceId);
- problemDetail.setProperty("timestamp", Instant.now());
- return problemDetail;
- }
-
- @ExceptionHandler(NullPointerException.class)
- public ProblemDetail handleNullPointerExceptions(NullPointerException ex) {
- String traceId = getOrGenerateTraceId();
- log.error("[TraceID: {}] Null pointer exception occurred: ", traceId, ex);
- ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
- HttpStatus.INTERNAL_SERVER_ERROR,
- "A null pointer exception occurred."
- );
- problemDetail.setProperty("traceId", traceId);
- problemDetail.setProperty("timestamp", Instant.now());
- return problemDetail;
- }
-}
-```
-
-**The GlobalExceptionHandler provides:**
-- ๐ก๏ธ **ProblemDetail RFC 9457** - Latest standard error format (supersedes RFC 7807)
-- โ
**Validation Error Handling** - Automatic `@Valid` annotation support
-- ๐ **Comprehensive Logging** - SLF4J integration with trace IDs
-- โฐ **Automatic Timestamps** - On all error responses
-- ๐ **Trace ID Generation** - Automatic UUID generation for all errors
-- ๐ **Null Pointer Protection** - Dedicated NullPointerException handling
-- ๐ **10 Exception Handlers** - Covers all common error scenarios
-
-**Example Validation Error Response:**
-```json
-{
- "type": "about:blank",
- "title": "Bad Request",
- "status": 400,
- "detail": "Validation Failed",
- "errors": {
- "email": "must be a well-formed email address",
- "name": "must not be blank"
- },
- "timestamp": "2026-02-02T10:30:45.123Z"
-}
-```
-
-You can also create custom exception handlers using ApiResponse:
-
-```java
-@ControllerAdvice
-public class CustomExceptionHandler {
-
- @ExceptionHandler(ResourceNotFoundException.class)
- public ResponseEntity> handleNotFound(ResourceNotFoundException ex) {
- return ApiResponse.status(ex.getMessage(), HttpStatus.NOT_FOUND);
- }
-
- @ExceptionHandler(IllegalArgumentException.class)
- public ResponseEntity> handleBadRequest(IllegalArgumentException ex) {
- return ApiResponse.status("Invalid request: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
- }
-}
-```
-
-## ๐ API Reference
-
-### Static Factory Methods
-
-| Method | Parameters | Return Type | HTTP Status | Description |
-|--------|-----------|-------------|-------------|-------------|
-| `success(String message)` | message | `ResponseEntity>` | 200 OK | Success without data |
-| `success(String message, T data)` | message, data | `ResponseEntity>` | 200 OK | Success with data |
-| `created(String message, T data)` | message, data | `ResponseEntity>` | 201 CREATED | Resource creation |
-| `status(String message, HttpStatus status)` | message, status | `ResponseEntity>` | Custom | Custom status without data |
-| `status(String message, T data, HttpStatus status)` | message, data, status | `ResponseEntity>` | Custom | Custom status with data |
-
-### Response Fields
-
-| Field | Type | Description |
-|-------|------|-------------|
-| `status` | `Integer` | HTTP status code (e.g., 200, 201, 404) |
-| `message` | `String` | Human-readable message describing the response |
-| `content` | `T` (Generic) | Response payload (can be any type or null) |
-| `timestamp` | `Instant` | ISO-8601 formatted UTC timestamp (auto-generated) |
-
-## ๐ Response Structure
-
-All **success responses** follow this consistent structure:
-
-```json
-{
- "status": 200,
- "message": "string",
- "content": {},
- "timestamp": "2026-02-06T10:30:45.123456Z"
-}
-```
-
-**Note:** `traceId` is NOT included in success responses. Trace IDs are only added to **error responses** (ProblemDetail format) by the GlobalExceptionHandler for debugging purposes.
-
-### Examples
-
-**Single Object:**
-```json
-{
- "status": 200,
- "message": "Product found",
- "content": {
- "id": 1,
- "name": "Laptop",
- "price": 999.99
- },
- "timestamp": "2026-02-01T10:30:45.123Z"
-}
-```
-
-**Array/List:**
-```json
-{
- "status": 200,
- "message": "Products retrieved",
- "content": [
- {"id": 1, "name": "Laptop"},
- {"id": 2, "name": "Mouse"}
- ],
- "timestamp": "2026-02-01T10:30:45.123Z"
-}
-```
-
-**No Content (Void):**
-```json
-{
- "status": 200,
- "message": "Product deleted successfully",
- "timestamp": "2026-02-01T10:30:45.123Z"
-}
-```
-
-**Error Response (ProblemDetail format with traceId):**
-```json
-{
- "type": "about:blank",
- "title": "Not Found",
- "status": 404,
- "detail": "Product not found with ID: 123",
- "traceId": "550e8400-e29b-41d4-a716-446655440000",
- "timestamp": "2026-02-01T10:30:45.123Z"
-}
-```
-
-## ๐ก Best Practices
-
-### 1. Use Appropriate Methods
-
-```java
-// โ
Use success() for standard operations
-return ApiResponse.success("Retrieved", data);
-
-// โ
Use created() for resource creation
-return ApiResponse.created("Created", newResource);
-
-// โ
Use status() for custom status codes
-return ApiResponse.status("Accepted", HttpStatus.ACCEPTED);
-```
-
-### 2. Write Clear Messages
-
-```java
-// โ
Good - Descriptive and specific
-return ApiResponse.success("User profile updated successfully", user);
-
-// โ Avoid - Too generic
-return ApiResponse.success("Success", user);
-```
-
-### 3. Leverage Generics
-
-```java
-// Specific types
-ResponseEntity> getUser();
-ResponseEntity>> getProducts();
-ResponseEntity>> getMetadata();
-ResponseEntity> deleteResource();
-```
-
-### 4. Custom Business Exceptions
-
-**Version 1.3.0 includes an abstract `ApiException` class** for creating domain-specific exceptions. The built-in `GlobalExceptionHandler` automatically handles them with the correct HTTP status:
-
-```java
-// Define custom exceptions
-public class ResourceNotFoundException extends ApiException {
- public ResourceNotFoundException(String resource, Long id) {
- super(String.format("%s not found with ID: %d", resource, id), HttpStatus.NOT_FOUND);
- }
-}
-
-public class InsufficientBalanceException extends ApiException {
- public InsufficientBalanceException(String accountId) {
- super("Insufficient balance in account: " + accountId, HttpStatus.PAYMENT_REQUIRED);
- }
-}
-
-// Use them in your service/controller
-@GetMapping("/{id}")
-public ResponseEntity> getUser(@PathVariable Long id) {
- User user = userRepository.findById(id)
- .orElseThrow(() -> new ResourceNotFoundException("User", id));
- return ApiResponse.success("User found", user);
-}
-```
-
-**Benefits:**
-- โ
No need to manually create `@ExceptionHandler` methods
-- โ
Automatic RFC 9457 ProblemDetail formatting
-- โ
Type-safe with compile-time checking
-- โ
Clean, readable code
-
-You can still create additional custom exception handlers if needed:
-
-```java
-@ControllerAdvice
-public class CustomExceptionHandler {
-
- @ExceptionHandler(ThirdPartyApiException.class)
- public ProblemDetail handleThirdPartyError(ThirdPartyApiException ex) {
- ProblemDetail problem = ProblemDetail.forStatusAndDetail(
- HttpStatus.BAD_GATEWAY, ex.getMessage()
- );
- problem.setProperty("timestamp", Instant.now());
- return problem;
- }
-}
-```
-
-### 5. RESTful Conventions
-
-```java
-@RestController
-@RequestMapping("/api/products")
-public class ProductController {
-
- @GetMapping("/{id}") // 200 OK
- public ResponseEntity> get(@PathVariable Long id) {
- return ApiResponse.success("Product found", productService.findById(id));
- }
-
- @PostMapping // 201 CREATED
- public ResponseEntity> create(@RequestBody ProductDto dto) {
- return ApiResponse.created("Product created", productService.create(dto));
- }
-
- @PutMapping("/{id}") // 200 OK
- public ResponseEntity> update(
- @PathVariable Long id, @RequestBody ProductDto dto) {
- return ApiResponse.success("Product updated", productService.update(id, dto));
- }
-
- @DeleteMapping("/{id}") // 200 OK
- public ResponseEntity> delete(@PathVariable Long id) {
- productService.delete(id);
- return ApiResponse.success("Product deleted");
- }
-}
-```
-
-## ๐งช Testing
-
-### Unit Testing with MockMvc
-
-```java
-@WebMvcTest(UserController.class)
-class UserControllerTest {
-
- @Autowired
- private MockMvc mockMvc;
-
- @MockBean
- private UserService userService;
-
- @Test
- void shouldReturnUserSuccessfully() throws Exception {
- User user = new User(1L, "John Doe", "john@example.com");
- when(userService.findById(1L)).thenReturn(user);
-
- mockMvc.perform(get("/api/users/1"))
- .andExpect(status().isOk())
- .andExpect(jsonPath("$.status").value(200))
- .andExpect(jsonPath("$.message").value("User retrieved successfully"))
- .andExpect(jsonPath("$.data.id").value(1))
- .andExpect(jsonPath("$.data.name").value("John Doe"))
- .andExpect(jsonPath("$.traceId").exists())
- .andExpect(jsonPath("$.timestamp").exists());
- }
-
- @Test
- void shouldReturnCreatedStatus() throws Exception {
- User newUser = new User(1L, "Jane Doe", "jane@example.com");
- when(userService.create(any())).thenReturn(newUser);
-
- mockMvc.perform(post("/api/users")
- .contentType(MediaType.APPLICATION_JSON)
- .content("{\"name\":\"Jane Doe\",\"email\":\"jane@example.com\"}"))
- .andExpect(status().isCreated())
- .andExpect(jsonPath("$.status").value(201))
- .andExpect(jsonPath("$.message").value("User created successfully"))
- .andExpect(jsonPath("$.data.id").value(1))
- .andExpect(jsonPath("$.traceId").exists());
- }
-
- @Test
- void shouldReturnValidationErrors() throws Exception {
- mockMvc.perform(post("/api/users")
- .contentType(MediaType.APPLICATION_JSON)
- .content("{\"name\":\"\",\"email\":\"invalid\"}"))
- .andExpect(status().isBadRequest())
- .andExpect(jsonPath("$.status").value(400))
- .andExpect(jsonPath("$.detail").value("Validation Failed"))
- .andExpect(jsonPath("$.errors.name").exists())
- .andExpect(jsonPath("$.errors.email").exists())
- .andExpect(jsonPath("$.timestamp").exists());
- }
-
- @Test
- void shouldHandleCustomException() throws Exception {
- when(userService.findById(999L))
- .thenThrow(new ResourceNotFoundException("User", 999L));
-
- mockMvc.perform(get("/api/users/999"))
- .andExpect(status().isNotFound())
- .andExpect(jsonPath("$.status").value(404))
- .andExpect(jsonPath("$.detail").value("User not found with ID: 999"))
- .andExpect(jsonPath("$.timestamp").exists());
- }
-}
-```
-
-### Integration Testing with TestRestTemplate
-
-```java
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
-class UserControllerIntegrationTest {
-
- @Autowired
- private TestRestTemplate restTemplate;
-
- @Test
- void shouldGetUser() {
- ResponseEntity response = restTemplate.getForEntity(
- "/api/users/1", ApiResponse.class);
-
- assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
- assertThat(response.getBody().getStatus()).isEqualTo(200);
- assertThat(response.getBody().getMessage()).contains("User");
- assertThat(response.getBody().getTraceId()).isNotNull();
- assertThat(response.getBody().getTimestamp()).isNotNull();
- }
-
- @Test
- void shouldCreateUser() {
- UserDto newUser = new UserDto("Jane Doe", "jane@example.com");
-
- ResponseEntity response = restTemplate.postForEntity(
- "/api/users", newUser, ApiResponse.class);
-
- assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
- assertThat(response.getBody().getStatus()).isEqualTo(201);
- }
-}
-```
-
-### Testing Custom Exceptions
-
-```java
-@ExtendWith(MockitoExtension.class)
-class CustomExceptionTest {
-
- @Test
- void shouldThrowResourceNotFoundException() {
- ResourceNotFoundException exception =
- new ResourceNotFoundException("User", 123L);
-
- assertThat(exception.getMessage()).isEqualTo("User not found with ID: 123");
- assertThat(exception.getStatus()).isEqualTo(HttpStatus.NOT_FOUND);
- }
-}
-```
-
-### Testing with WebTestClient (WebFlux)
-
-If you adapt the library for reactive applications:
-
-```java
-@WebFluxTest(UserController.class)
-class UserControllerWebFluxTest {
-
- @Autowired
- private WebTestClient webTestClient;
-
- @MockBean
- private UserService userService;
-
- @Test
- void shouldReturnUser() {
- User user = new User(1L, "John Doe", "john@example.com");
- when(userService.findById(1L)).thenReturn(Mono.just(user));
-
- webTestClient.get()
- .uri("/api/users/1")
- .exchange()
- .expectStatus().isOk()
- .expectBody()
- .jsonPath("$.status").isEqualTo(200)
- .jsonPath("$.data.name").isEqualTo("John Doe")
- .jsonPath("$.traceId").exists();
- }
-}
-```
- User newUser = new User(1L, "Jane Doe", "jane@example.com");
- when(userService.create(any())).thenReturn(newUser);
-
- mockMvc.perform(post("/api/users")
- .contentType(MediaType.APPLICATION_JSON)
- .content("{\"name\":\"Jane Doe\",\"email\":\"jane@example.com\"}"))
- .andExpect(status().isCreated())
- .andExpect(jsonPath("$.status").value(201))
- .andExpect(jsonPath("$.message").value("User created successfully"))
- .andExpect(jsonPath("$.data.id").value(1))
- .andExpect(jsonPath("$.traceId").exists());
- }
-}
-```
-
-## ๐๏ธ Architecture & Design Principles
-
-### Thread Safety & Immutability
-
-The `ApiResponse` class is designed with **immutability** at its core:
-
-- All fields are declared as `final`
-- No setter methods exist (only getters)
-- Uses a custom inner Builder class for object construction
-- Thread-safe by design - can be safely shared across threads
-
-```java
-// Once created, the response cannot be modified
-ApiResponse response = new ApiResponse.ApiResponseBuilder()
- .message("Success")
- .content(user)
- .build();
-
-// This is thread-safe and can be safely cached or shared
-```
-
-### Dependency Management
-
-The library uses **provided scope** for Spring Boot:
-
-```xml
-
- org.springframework.boot
- spring-boot-starter-web
- provided
-
-```
-
-**Benefits:**
-- โ
**No Dependency Conflicts** - Uses your application's existing Spring Boot version
-- โ
**Zero External Dependencies** - Pure Java implementation, no Lombok or other libraries required
-- โ
**Zero Bloat** - Adds only ~10KB to your application
-- โ
**Version Flexibility** - Compatible with Spring Boot 3.2.0 - 4.0.3 and Java 17+
-
-### Auto-Configuration Architecture
-
-```
-โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-โ Spring Boot Application Starts โ
-โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ
- โผ
-โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-โ Reads META-INF/spring/ โ
-โ org.springframework.boot.autoconfigure โ
-โ .AutoConfiguration.imports โ
-โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ
- โผ
-โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-โ Loads ApiResponseAutoConfiguration โ
-โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ
- โผ
-โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-โ Registers GlobalExceptionHandler Bean โ
-โ (as @RestControllerAdvice) โ
-โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ
- โผ
-โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-โ Ready to Handle Exceptions Automatically โ
-โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-```
-
-### Design Patterns Used
-
-- **Factory Pattern** - Static factory methods (`success()`, `created()`, `status()`)
-- **Builder Pattern** - Custom inner Builder class for flexible object construction
-- **Template Method Pattern** - `ApiException` abstract class for custom exceptions
-- **Advisor Pattern** - `GlobalExceptionHandler` with `@RestControllerAdvice`
-
-## ๐ OpenAPI/Swagger Integration
-
-Works seamlessly with SpringDoc OpenAPI:
-
-```java
-@Operation(summary = "Get user by ID", description = "Returns a single user")
-@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "User found"),
- @ApiResponse(responseCode = "404", description = "User not found")
-})
-@GetMapping("/{id}")
-public ResponseEntity> getUser(@PathVariable Long id) {
- User user = userService.findById(id);
- return ApiResponse.success("User retrieved successfully", user);
-}
-```
-
-**Add SpringDoc dependency:**
-```xml
-
- org.springdoc
- springdoc-openapi-starter-webmvc-ui
- 2.3.0
-
-```
-
-## ๐ Compatibility Matrix
-
-### Tested Compatibility
-
-| Library Version | Java Version | Spring Boot Version | Status |
-|----------------|--------------|---------------------|--------|
-| 1.3.0 | 17, 21+ | 3.2.0 - 4.0.3 | โ
Tested |
-| 1.2.0 | 17, 21+ | 3.2.0 - 4.0.3 | โ
Tested |
-| 1.1.1 | 17, 21+ | 3.2.0 - 4.0.3 | โ
Tested |
-| 1.1.0 | 17, 21+ | 3.2.0 - 4.0.2 | โ
Tested |
-| 1.0.0 | 17, 21+ | 3.2.0+ | โ
Tested |
-
-### Version Requirements
-
-**Minimum Requirements:**
-- **Java:** 17 or higher
-- **Spring Boot:** 3.2.0 or higher (tested up to 4.0.3)
-- **Dependencies:** None (pure Java implementation)
-
-**Recommended:**
-- **Java:** 21 (LTS)
-- **Spring Boot:** 3.4.x or 4.0.x (fully compatible with Spring Boot 4)
-
-### Spring Boot 4.x Support
-
-โ
**Full Spring Boot 4.0.3 Compatibility**
-- The library has been tested and verified to work with Spring Boot 4.0.3
-- All features including auto-configuration work seamlessly
-- No breaking changes when upgrading from Spring Boot 3.x to 4.x
-- Uses provided scope dependencies to avoid version conflicts
-
-### Framework Compatibility
-
-| Framework | Supported | Notes |
-|-----------|-----------|-------|
-| Spring Boot 4.x | โ
Yes | Full support with version 4.0.3 |
-| Spring Boot 3.x | โ
Yes | Full support with auto-configuration |
-| Spring Boot 2.x | โ No | Use Spring Boot 3.x+ |
-| Spring WebFlux | โ ๏ธ Partial | Manual adaptation required |
-| Micronaut | โ No | Spring-specific features used |
-| Quarkus | โ No | Spring-specific features used |
-
-### Build Tools
-
-| Build Tool | Supported | Configuration |
-|------------|-----------|---------------|
-| Maven | โ
Yes | Native support |
-| Gradle | โ
Yes | Groovy & Kotlin DSL |
-| Gradle (Groovy) | โ
Yes | `implementation 'io.github.og4dev:og4dev-spring-response:1.0.0'` |
-| Gradle (Kotlin) | โ
Yes | `implementation("io.github.og4dev:og4dev-spring-response:1.0.0")` |
-
-## ๐ง Troubleshooting
-
-### Common Issues & Solutions
-
-#### 1. GlobalExceptionHandler Not Working
-
-**Problem:** Exceptions are not being caught by the GlobalExceptionHandler.
-
-**Solution:**
-- Check that auto-configuration is not excluded
-- Verify Spring Boot version is 3.2.0+
-
-```java
-// Verify auto-configuration is active
-@SpringBootApplication
-// Do NOT exclude ApiResponseAutoConfiguration
-public class Application {
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
-}
-```
-
-#### 2. Response Fields Not Serializing
-
-**Problem:** Some fields like `status`, `message`, or `content` are missing in JSON responses.
-
-**Solution:**
-- The library uses `@JsonInclude(JsonInclude.Include.NON_NULL)` to exclude null fields
-- This is intentional behavior - only non-null fields are included in the response
-- To include all fields, you would need to customize Jackson's configuration in your application
-
-#### 3. Auto-Configuration Not Working
-
-**Problem:** GlobalExceptionHandler is not being picked up automatically.
-
-**Solution:**
-- Ensure you're using Spring Boot 3.2.0 or higher
-- Check that the library JAR is on the classpath
-- Verify `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` exists in the JAR
-- Check application logs for auto-configuration reports
-
-#### 4. Version Conflicts with Spring Boot
- .status(200)
- .traceId(UUID.randomUUID()) // Must set manually
- .message("Success")
- .content(data)
- .build());
-```
-
-- For **error responses**, trace IDs are **automatically generated** by GlobalExceptionHandler
-- All error logs and responses have matching trace IDs for easy correlation
-
-#### 4. Dependency Conflicts
-
-**Problem:** Version conflicts with Spring Boot.
-
-**Solution:**
-- The library uses `provided` scope for Spring Boot - it won't conflict
-- Ensure your application has Spring Boot Web dependency:
-
-```xml
-
- org.springframework.boot
- spring-boot-starter-web
-
-```
-
-#### 5. Custom Exception Not Being Caught
-
-**Problem:** Custom `ApiException` subclass not returning ProblemDetail.
-
-**Solution:**
-- Ensure your exception extends `ApiException`
-- Verify the exception is actually being thrown
-- Check GlobalExceptionHandler is registered
-
-```java
-// โ
Correct
-public class MyException extends ApiException {
- public MyException(String message) {
- super(message, HttpStatus.BAD_REQUEST);
- }
-}
-
-// โ Wrong - must extend ApiException
-public class MyException extends RuntimeException {
- // ...
-}
-```
-
-#### 6. Timestamp Format Issues
-
-**Problem:** Timestamp format not as expected.
-
-**Solution:**
-- The library uses `Instant` (UTC) by default
-- Configure Jackson if you need different format:
-
-```java
-@Configuration
-public class JacksonConfig {
- @Bean
- public ObjectMapper objectMapper() {
- ObjectMapper mapper = new ObjectMapper();
- mapper.registerModule(new JavaTimeModule());
- mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
- return mapper;
- }
-}
-```
-
-#### 7. Auto-Configuration Not Loading
-
-**Problem:** Auto-configuration doesn't work.
-
-**Solution:**
-- Verify you're using Spring Boot 3.x (not 2.x)
-- Check the JAR includes `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`
-- Clear Maven/Gradle cache and rebuild:
-
-```bash
-# Maven
-mvn clean install
-
-# Gradle
-./gradlew clean build --refresh-dependencies
-```
-
-#### 8. Jackson Serialization Issues
-
-**Problem:** Fields are not serializing as expected or timestamp format is wrong.
-
-**Solution:**
-- The library uses Jackson's `@JsonInclude(NON_NULL)` by default
-- Ensure you have `jackson-datatype-jsr310` for Java 8+ date/time support (included in Spring Boot)
-- Configure Jackson if needed:
-
-```java
-@Configuration
-public class JacksonConfig {
- @Bean
- public ObjectMapper objectMapper() {
- ObjectMapper mapper = new ObjectMapper();
- mapper.registerModule(new JavaTimeModule());
- mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
- mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
- return mapper;
- }
-}
-```
-
-#### 9. UUID Not Serializing Properly
-
-**Problem:** TraceId appears as an object instead of string.
-
-**Solution:**
-- This shouldn't happen with standard Jackson configuration
-- Verify Jackson version is compatible with Spring Boot
-- UUID is serialized as string by default in Jackson
-
-```json
-// โ
Correct
-"traceId": "550e8400-e29b-41d4-a716-446655440000"
-
-// โ Wrong (shouldn't happen)
-"traceId": {"mostSigBits": 123, "leastSigBits": 456}
-```
-
-### Getting Help
-
-If you encounter issues not covered here:
-
-1. **Check the Issues:** [GitHub Issues](https://github.com/OG4Dev/og4dev-spring-response/issues)
-2. **Review JavaDocs:** All classes are fully documented
-3. **Enable Debug Logging:**
- ```properties
- logging.level.io.github.og4dev=DEBUG
- ```
-4. **Open an Issue:** Provide minimal reproducible example
-
-## โ FAQ
-
-### How do I use trace IDs for debugging? **
-
-**Error responses** automatically include a `traceId` (UUID) in the ProblemDetail format for request tracking and debugging. Success responses (ApiResponse) do NOT include traceId in the response body, but you can add trace IDs via headers using the TraceIdFilter.
-
-**Key Points:**
-- โ
**Error responses** - traceId is automatically included in ProblemDetail JSON
-- โ **Success responses** - traceId is NOT in ApiResponse JSON (use headers instead)
-- ๐ **Logging** - All exceptions are logged with [TraceID: xxx] for correlation
-
-For comprehensive distributed tracing with MDC integration and header propagation, see the **Distributed Tracing** section above.
-
-**Quick Example with TraceIdFilter:**
-
-```java
-// 1. Register TraceIdFilter for header propagation
-@Configuration
-public class FilterConfig {
- @Bean
- public FilterRegistrationBean traceIdFilter() {
- FilterRegistrationBean registration = new FilterRegistrationBean<>();
- registration.setFilter(new TraceIdFilter());
- registration.addUrlPatterns("/*");
- registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
- return registration;
- }
-}
-
-// 2. Use in your service (trace ID automatically in logs)
-@Service
-@Slf4j
-public class UserService {
- public User createUser(UserDto dto) {
- log.info("Creating user with email: {}", dto.getEmail()); // [traceId] in logs
- // ... business logic
- return user;
- }
-}
-```
-
-**For industry-standard header propagation** (accepting trace IDs from upstream services), use the **[Enhanced TraceIdFilter](#enhanced-traceidfilter-with-header-propagation)** which supports:
-- `X-Trace-Id` and `X-Request-ID` headers
-- `X-B3-TraceId` (Zipkin format)
-- `traceparent` (W3C Trace Context)
-- Automatic response header injection
-}
-```
-
-**Response:**
-```json
-{
- "status": 200,
- "message": "User created",
- "content": {...},
- "timestamp": "2026-02-06T10:30:45.123Z"
-}
-```
-
-**Note:** The traceId is in the MDC for logging and can be added to response headers, but is NOT in the ApiResponse JSON body.
-
-**Logs (with traceId from MDC):**
-```
-2026-02-06 10:30:45.123 [550e8400-e29b-41d4-a716-446655440000] INFO c.e.s.UserService - Creating user with email: john@example.com
-```
-```
-
-For more details on MDC integration, propagating trace IDs to downstream services, and end-to-end tracing examples, see the **[Distributed Tracing](#-distributed-tracing-enhanced-in-v200)** section.
-```
-
-### How do I create custom business exceptions?
-
-Extend the abstract `ApiException` class to create domain-specific exceptions:
-
-```java
-public class ResourceNotFoundException extends ApiException {
- public ResourceNotFoundException(String resource, Long id) {
- super(String.format("%s not found with ID: %d", resource, id), HttpStatus.NOT_FOUND);
- }
-}
-
-public class DuplicateResourceException extends ApiException {
- public DuplicateResourceException(String message) {
- super(message, HttpStatus.CONFLICT);
- }
-}
-```
-
-Then throw them in your code - the `GlobalExceptionHandler` will automatically convert them to RFC 9457 ProblemDetail responses:
-
-```java
-@GetMapping("/{id}")
-public ResponseEntity> getUser(@PathVariable Long id) {
- User user = userRepository.findById(id)
- .orElseThrow(() -> new ResourceNotFoundException("User", id));
- return ApiResponse.success("User found", user);
-}
-```
-
-### How do I use the built-in GlobalExceptionHandler?
-
-The `GlobalExceptionHandler` is **automatically configured** via Spring Boot Auto-Configuration. No manual setup is required!
-
-Simply add the library dependency, and the exception handler will be active immediately. The auto-configuration mechanism automatically registers the handler when the library is detected on the classpath.
-
-### Can I customize or extend the GlobalExceptionHandler?
-
-Yes! You can add your own `@ControllerAdvice` handlers alongside the built-in one:
-
-```java
-@ControllerAdvice
-public class CustomExceptionHandler {
-
- @ExceptionHandler(ResourceNotFoundException.class)
- public ResponseEntity> handleNotFound(ResourceNotFoundException ex) {
- return ApiResponse.status(ex.getMessage(), HttpStatus.NOT_FOUND);
- }
-
- @ExceptionHandler(UnauthorizedException.class)
- public ProblemDetail handleUnauthorized(UnauthorizedException ex) {
- ProblemDetail problem = ProblemDetail.forStatusAndDetail(
- HttpStatus.UNAUTHORIZED, ex.getMessage()
- );
- problem.setProperty("timestamp", Instant.now());
- return problem;
- }
-}
-```
-
-### How do I disable the GlobalExceptionHandler?
-
-You can disable it by excluding the auto-configuration:
-
-```java
-@SpringBootApplication(exclude = ApiResponseAutoConfiguration.class)
-public class Application {
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
-}
-```
-
-Or in `application.properties`:
-```properties
-spring.autoconfigure.exclude=io.github.og4dev.config.ApiResponseAutoConfiguration
-```
-
-### How do I customize the timestamp format?
-
-Configure Jackson's ObjectMapper:
-
-```java
-@Configuration
-public class JacksonConfig {
- @Bean
- public ObjectMapper objectMapper() {
- ObjectMapper mapper = new ObjectMapper();
- mapper.registerModule(new JavaTimeModule());
- mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
- return mapper;
- }
-}
-```
-
-### Can I add custom fields?
-
-The library provides a standard structure. To add custom fields, wrap the response:
-
-```java
-public class ExtendedResponse {
- private ApiResponse response;
- private String requestId;
- private Map metadata;
- // getters and setters
-}
-```
-
-### Does this work with Spring WebFlux?
-
-Currently designed for Spring MVC. For WebFlux, you'd need to adapt it to work with `Mono>>`.
-
-### How do I handle paginated responses?
-
-Use Spring's `Page` as the data type:
-
-```java
-@GetMapping
-public ResponseEntity>> getUsers(Pageable pageable) {
- Page users = userService.findAll(pageable);
- return ApiResponse.success("Users retrieved", users);
-}
-```
-
-### Why use this over plain ResponseEntity?
-
-| Plain ResponseEntity | ApiResponse |
-|---------------------|-------------|
-| Inconsistent structure | โ
Standardized everywhere |
-| Manual timestamps | โ
Automatic |
-| No trace IDs | โ
Built-in UUID trace IDs |
-| No status in body | โ
Status code included in response |
-| Manual exception handling | โ
Custom ApiException support |
-| More boilerplate | โ
Concise factory methods |
-| No message field | โ
Always includes message |
-
-## ๐ Performance & Best Practices
-
-### Performance Characteristics
-
-- **Response Creation:** < 1ms (simple object instantiation with builder pattern)
-- **Memory Footprint:** ~200 bytes per response object (excluding data payload)
-- **Thread Safety:** 100% thread-safe (immutable design with final fields)
-- **GC Impact:** Minimal (uses immutable objects, eligible for quick collection)
-- **JSON Serialization:** Optimized with `@JsonInclude(NON_NULL)` to reduce payload size
-- **UUID Generation:** Negligible overhead (~0.1ms per UUID using `UUID.randomUUID()`)
-- **Timestamp Generation:** Negligible overhead (~0.01ms using `Instant.now()`)
-
-### Benchmark Results (Approximate)
-
-| Operation | Time | Notes |
-|-----------|------|-------|
-| `ApiResponse.success()` | ~0.5ms | Including UUID and timestamp generation |
-| `ApiResponse.created()` | ~0.5ms | Same as success() |
-| `ApiResponse.builder().build()` | ~0.3ms | Manual builder without factory methods |
-| JSON Serialization (small DTO) | ~1-2ms | Standard Jackson performance |
-| GlobalExceptionHandler catch | ~0.1ms | Minimal overhead for exception transformation |
-
-### Best Practices
-
-#### 1. Prefer Factory Methods Over Builder
-
-```java
-// โ
RECOMMENDED - Auto-generates trace ID and timestamp
-return ApiResponse.success("User found", user);
-
-// โ ๏ธ AVOID - More verbose, manual field management
-return ResponseEntity.ok(ApiResponse.builder()
- .status(200)
- .traceId(UUID.randomUUID())
- .message("User found")
- .data(user)
- .timestamp(Instant.now())
- .build());
-```
-
-#### 2. Use Appropriate HTTP Status Codes
-
-```java
-// โ
POST - Use created() for 201
-@PostMapping
-public ResponseEntity> create(@RequestBody UserDto dto) {
- return ApiResponse.created("User created", userService.create(dto));
-}
-
-// โ
DELETE - Use success() with no data
-@DeleteMapping("/{id}")
-public ResponseEntity> delete(@PathVariable Long id) {
- userService.delete(id);
- return ApiResponse.success("User deleted");
-}
-
-// โ
Custom status - Use status() method
-@GetMapping("/health")
-public ResponseEntity> health() {
- return ApiResponse.status("Service degraded", HttpStatus.SERVICE_UNAVAILABLE);
-}
-```
-
-#### 3. Write Descriptive Messages
-
-```java
-// โ
GOOD - Clear and actionable
-return ApiResponse.success("User profile updated successfully", updatedUser);
-
-// โ BAD - Too generic
-return ApiResponse.success("Success", updatedUser);
-
-// โ BAD - Technical jargon
-return ApiResponse.success("User entity persisted to database", updatedUser);
-```
-
-#### 4. Handle Exceptions Properly
-
-```java
-// โ
GOOD - Use custom ApiException
-public class InsufficientFundsException extends ApiException {
- public InsufficientFundsException(String accountId) {
- super("Insufficient funds in account: " + accountId, HttpStatus.PAYMENT_REQUIRED);
- }
-}
-
-// In your service
-if (account.getBalance() < amount) {
- throw new InsufficientFundsException(account.getId());
-}
-
-// โ AVOID - Generic exceptions
-throw new RuntimeException("Not enough money");
-```
-
-#### 5. Leverage Trace IDs for Debugging
-
-```java
-// Log the trace ID from incoming requests
-@PostMapping
-public ResponseEntity> createUser(@RequestBody UserDto dto) {
- UUID traceId = UUID.randomUUID();
- log.info("Processing user creation request, traceId: {}", traceId);
-
- User user = userService.create(dto);
-
- // Create response with same trace ID
- ApiResponse response = ApiResponse.builder()
- .status(HttpStatus.CREATED.value())
- .traceId(traceId)
- .message("User created successfully")
- .data(user)
- .build();
-
- return ResponseEntity.status(HttpStatus.CREATED).body(response);
-}
-```
-
-#### 6. Caching Considerations
-
-```java
-// โ ๏ธ CAUTION - Response includes timestamp, making caching difficult
-// Consider extracting just the data for caching:
-
-@Cacheable("users")
-public User getUserData(Long id) {
- return userRepository.findById(id).orElseThrow();
-}
-
-@GetMapping("/{id}")
-public ResponseEntity> getUser(@PathVariable Long id) {
- // Fresh response wrapper, cached data
- return ApiResponse.success("User found", getUserData(id));
-}
-```
-
-## ๐ Migration Guide
-
-### From v1.0.0 to v1.1.0
-
-This is a minor release with **new security features** and documentation fixes:
-
-#### โจ New Features:
-- โ
**Strict JSON Validation** - New `strictJsonCustomizer()` method that adds:
- - `FAIL_ON_UNKNOWN_PROPERTIES` - Rejects JSON with unexpected fields (prevents mass assignment attacks)
- - `ACCEPT_CASE_INSENSITIVE_ENUMS` - Flexible enum handling (e.g., "ACTIVE", "active", "Active")
- - **Automatic XSS Prevention** - Detects and rejects HTML tags in string inputs
- - **Automatic String Trimming** - Removes leading/trailing whitespace
- - Throws `IllegalArgumentException` with message: "Security Error: HTML tags or XSS payloads are not allowed in the request."
-
-#### ๐ Documentation Fixes:
-- โ
Fixed incorrect package name references in README (`io.github.pasinduog` โ `io.github.og4dev`)
-
-#### ๐ Migration Steps:
-**No code changes required!** The new security features are automatically applied.
-
-If you need to disable strict validation (e.g., to accept HTML content):
-```java
-@Configuration
-public class CustomConfig {
- @Bean
- @Primary // Override the default
- public JsonMapperBuilderCustomizer myCustomizer() {
- return builder -> {
- // Your custom configuration
- };
- }
-}
-```
-
-### Getting Started (New Users)
-
-Simply add the dependency to your project:
-
-```xml
-
- io.github.og4dev
- og4dev-spring-response
- 1.3.0
-
-```
-
-All features are automatically enabled through Spring Boot auto-configuration. No manual configuration needed!
-
-## ๐ Security Considerations
-
-### ๐ก๏ธ Built-in Security Features (Automatic)
-
-The library includes **three automatic security protections** that are enabled by default:
-
-#### 1. โ
Strict JSON Property Validation (Mass Assignment Prevention)
-
-**Automatically enabled** - Rejects unknown properties to prevent mass assignment attacks:
-
-```java
-// Your DTO
-public class UserUpdateDto {
- private String name;
- private String email;
-}
-
-// โ
Valid request
-{"name": "John", "email": "john@example.com"}
-
-// โ Automatically rejected with 400 Bad Request
-{"name": "John", "email": "john@example.com", "isAdmin": true}
-```
-
-**What it prevents:**
-- Privilege escalation attempts
-- Data injection attacks
-- Unauthorized field modifications
-
-#### 2. โ
Automatic XSS Prevention (HTML Tag Rejection)
-
-**Automatically enabled** - All string inputs are validated for HTML tags, and requests with HTML content are rejected:
-
-```java
-// โ
Valid input - Clean text
-{"comment": "This is a safe comment"}
-
-// โ Rejected with 400 Bad Request
-{"comment": ""}
-
-// Error response:
-{
- "status": 400,
- "detail": "Security Error: HTML tags or XSS payloads are not allowed in the request.",
- "traceId": "550e8400-e29b-41d4-a716-446655440000",
- "timestamp": "2026-02-16T10:30:45.123Z"
-}
-```
-
-**What it prevents:**
-- Cross-Site Scripting (XSS) attacks
-- HTML injection
-- JavaScript injection
-- XML/XHTML injection attempts
-
-**Note:** Fail-fast approach - malicious content never enters your system.
-
-#### 3. โ
Automatic String Sanitization
-
-**Automatically enabled** - Trims whitespace and preserves null values:
-
-```java
-// Input
-{"name": " John Doe ", "middleName": null}
-
-// Automatically processed to
-{"name": "John Doe", "middleName": null}
-```
-
-**See also:** [Built-in Security Features](#-built-in-security-features) for detailed documentation.
-
----
-
-### 1. Exception Message Sanitization
-
-The library's `GlobalExceptionHandler` provides safe defaults, but be mindful:
-
-```java
-// โ
SAFE - No sensitive data
-throw new ApiException("User not found", HttpStatus.NOT_FOUND);
-
-// โ ๏ธ CAUTION - May leak sensitive information
-throw new ApiException("Database connection failed: " + sqlException.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
-
-// โ
BETTER - Generic message
-throw new ApiException("An internal error occurred", HttpStatus.INTERNAL_SERVER_ERROR);
-```
-
-### 2. Trace ID Privacy
-
-Trace IDs are UUIDs and don't contain sensitive information. However:
-
-- **Don't log sensitive data** alongside trace IDs
-- **Consider rate limiting** to prevent trace ID enumeration
-- **Rotate logs** regularly to limit exposure
-
-```java
-// โ
SAFE
-log.info("User created successfully, traceId: {}", traceId);
-
-// โ UNSAFE - Logs password
-log.info("User created, traceId: {}, password: {}", traceId, password);
-```
-
-### 3. Stack Trace Exposure
-
-The `GlobalExceptionHandler` **never exposes stack traces** to clients. Stack traces are:
-- โ
Logged server-side for debugging
-- โ Never sent in API responses
-- โ
Replaced with generic messages
-
-### 4. Validation Error Details
-
-Validation errors include field names and constraints:
-
-```json
-{
- "status": 400,
- "detail": "Validation Failed",
- "errors": {
- "email": "must be a well-formed email address",
- "password": "must not be blank"
- }
-}
-```
-
-**Security Tips:**
-- โ
Don't include sensitive field values in error messages
-- โ
Use generic constraint messages for sensitive fields
-- โ
Consider custom validators for sensitive data
-
-```java
-public class SensitiveDto {
- @NotBlank(message = "Required field is missing") // Generic message
- private String creditCardNumber;
-
- @Pattern(regexp = "...", message = "Invalid format") // No details
- private String ssn;
-}
-```
-
-### 5. CORS & Security Headers
-
-The library doesn't interfere with Spring Security or CORS configuration:
-
-```java
-@Configuration
-public class SecurityConfig {
- @Bean
- public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
- http
- .cors(cors -> cors.configurationSource(corsConfigurationSource()))
- .csrf(csrf -> csrf.disable())
- // ... other configurations
- return http.build();
- }
-}
-```
-
-### 6. Dependency Security
-
-The library uses **provided scope** dependencies:
-- โ
No transitive dependency vulnerabilities
-- โ
Uses your application's Spring Boot version
-- โ
No additional security surface area
-
-**Verify with:**
-```bash
-mvn dependency:tree -Dincludes=io.github.og4dev:og4dev-spring-response
-```
-
-### 7. ProblemDetail Information Disclosure
-
-RFC 9457 ProblemDetail responses (supersedes RFC 7807) include:
-- `type` - URI reference (defaults to "about:blank")
-- `title` - Short, human-readable summary
-- `status` - HTTP status code
-- `detail` - Human-readable explanation
-- `instance` - URI reference (not used by default)
-
-**Best Practice:** Don't include internal system details in error messages.
-
-## ๐ค Contributing
-
-**We welcome and encourage contributions from everyone!** ๐
-
-This project is licensed under the **Apache License 2.0**, which means:
-- โ
**Anyone can contribute** - The project is open to all developers
-- โ
**Your contributions are protected** - No one can claim exclusive ownership of your work
-- โ
**You retain copyright** - You keep rights to your work while granting usage rights to the project
-- โ
**Fair and equal terms** - All contributions are made under the same Apache 2.0 terms
-
-### ๐ Contributor License Agreement
-
-By contributing to this project, you agree that:
-
-1. **Your contributions** will be licensed under the Apache License 2.0
-2. **You grant** the project maintainers and users a perpetual, worldwide license to use your contributions
-3. **You retain** copyright to your contributions
-4. **You confirm** that you have the right to submit the contribution and grant these rights
-5. **Your work is protected** - No one (including the project maintainer) can claim exclusive ownership of your contributions or publish them elsewhere as their own
-
-### ๐ What Apache 2.0 Means for Contributors
-
-โ
**Your Rights:**
-- You keep copyright to your contributions
-- Your name is attributed in the project
-- You can use your contributions elsewhere
-- You're protected from patent claims
-
-๐ก๏ธ **Project Protection:**
-- **Prevents anyone from copyrighting the project** - No one can claim exclusive ownership
-- Ensures the project remains open source
-- Protects all contributors equally
-- Maintains free usage for everyone
-- **Cannot be published elsewhere as proprietary** - Derivative works must maintain the Apache 2.0 License
-
-### How to Contribute
-
-1. **Fork the repository** on GitHub
- - Navigate to https://github.com/OG4Dev/og4dev-spring-response
- - Click the "Fork" button in the top-right corner
-
-2. **Clone your fork** locally
- ```bash
- git clone https://github.com/YOUR_USERNAME/api-response.git
- cd api-response
- ```
-
-3. **Create a feature branch**
- ```bash
- git checkout -b feature/amazing-feature
- ```
-
-4. **Make your changes**
- - Follow the existing code style and conventions
- - Add JavaDoc comments for all public methods and classes
- - Ensure all existing tests pass (when tests are added)
- - Keep changes focused and atomic
-
-5. **Commit your changes** (follow conventional commit format)
- ```bash
- git commit -m 'feat: add amazing feature'
- ```
-
-6. **Push to your fork**
- ```bash
- git push origin feature/amazing-feature
- ```
-
-7. **Open a Pull Request** on the main repository
- - Go to the original repository on GitHub
- - Click "New Pull Request"
- - Select your branch and describe your changes in detail
- - Reference any related issues
-
-### Contribution Guidelines
-
-#### Code Quality Standards
-- โ
Follow existing code style and conventions
-- โ
Add comprehensive JavaDoc comments for all public methods and classes
-- โ
Include explicit constructor documentation for all constructors
-- โ
Ensure all existing tests pass
-- โ
Keep changes focused and atomic
-- โ
Update README.md if adding new features or changing behavior
-- โ
Verify zero Javadoc warnings (`mvn javadoc:javadoc` should run cleanly)
-
-#### Commit Message Format
-Follow [Conventional Commits](https://www.conventionalcommits.org/) specification:
-
-```bash
-# Format: ():
-
-# Types:
-feat: add new response wrapper method
-fix: correct trace ID generation
-docs: update installation instructions
-refactor: improve exception handling
-test: add unit tests for ApiResponse
-style: format code and fix whitespace
-chore: update dependencies
-perf: optimize response creation
-```
-
-**Examples:**
-```bash
-feat(dto): add pagination support to ApiResponse
-fix(exception): resolve NPE in GlobalExceptionHandler
-docs(readme): add contributing guidelines
-test(response): add unit tests for success factory methods
-```
-
-#### Pull Request Process
-1. **Update documentation** - Modify README.md if adding features or changing behavior
-2. **Add JavaDoc** - Document all new public methods and classes with zero warnings
-3. **Write clear PR description**:
- - What problem does this solve?
- - What changes were made?
- - How was it tested?
- - Any breaking changes?
-4. **Reference issues** - Link related issues using `#issue-number` or `Fixes #issue-number`
-5. **Wait for review** - Maintainer will review and provide feedback
-6. **Address feedback** - Make requested changes and push updates
-7. **Squash commits** - Clean up commit history if needed before merge
-
-### Development Setup
-
-```bash
-# Clone the repository
-git clone https://github.com/OG4Dev/og4dev-spring-response.git
-cd og4dev-spring-response
-
-# Build the project
-mvn clean install
-
-# Generate JavaDoc (builds with zero warnings)
-mvn javadoc:javadoc
-
-# Generate JavaDoc JAR (clean build with comprehensive documentation)
-mvn javadoc:jar
-
-# Package for Maven Central (requires GPG key)
-mvn clean deploy -P release
-```
-
-**Note:** The project includes explicit constructor documentation for all classes, ensuring zero Javadoc warnings during the build process. All constructors (including Spring bean constructors) are properly documented.
-
-### Project Structure for Contributors
-
-```
-og4dev-spring-response/
-โโโ src/
-โ โโโ main/
-โ โ โโโ java/io/github/og4dev/
-โ โ โ โโโ config/ # Auto-configuration classes
-โ โ โ โโโ dto/ # Response wrapper classes
-โ โ โ โโโ exception/ # Exception handling
-โ โ โโโ resources/
-โ โ โโโ META-INF/spring/ # Auto-configuration metadata
-โ โโโ test/java/ # Unit tests (to be added)
-โโโ pom.xml # Maven configuration
-โโโ README.md # Documentation
-```
-
-### What We're Looking For
-
-- ๐ Bug fixes
-- ๐ Documentation improvements
-- โจ New features (discuss in issue first)
-- ๐งช Test coverage improvements
-- ๐จ Code quality enhancements
-- ๐ Internationalization support
-
-### Code of Conduct
-
-- Be respectful and inclusive
-- Provide constructive feedback
-
-## ๐ License
-
-This project is licensed under the **Apache License 2.0** - see the [LICENSE](LICENSE) file for full details.
-
-### What This Means
-
-The Apache 2.0 License is a permissive open-source license that:
-
-#### โ
**Allows You To:**
-- โ
Use this library in personal, commercial, or proprietary projects
-- โ
Modify and distribute the source code
-- โ
Sublicense the code
-- โ
Use the software for any purpose (private, commercial, etc.)
-- โ
Patent protection - contributors grant you patent rights
-
-#### ๐ก๏ธ **Protects The Project:**
-- ๐ก๏ธ **No one can claim ownership** - Others cannot copyright this work as their own
-- ๐ก๏ธ **Attribution required** - Anyone using or modifying this code must give credit
-- ๐ก๏ธ **License propagation** - Modified versions must include the Apache 2.0 License
-- ๐ก๏ธ **Trademark protection** - Project name and trademarks remain protected
-- ๐ก๏ธ **No liability** - Software is provided "as-is" without warranty
-
-#### ๐ค **For Contributors:**
-- ๐ค **Open contribution** - Anyone can contribute under the same terms
-- ๐ค **Grant of rights** - By contributing, you grant Apache 2.0 rights to your contributions
-- ๐ค **Your work is protected** - Contributions are attributed and cannot be claimed by others
-- ๐ค **Patent peace** - Contributors cannot sue users for patent infringement related to their contributions
-
-### Key License Terms
-
-```
-Copyright 2026 Pasindu OG
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-```
-
-### Summary for Users
-
-โ
**You CAN:**
-- Use in commercial products
-- Modify the code
-- Distribute copies
-- Grant sublicenses
-- Use privately
-
-โ **You CANNOT:**
-- Hold the author liable
-- Use contributor names for endorsement
-- Remove copyright notices
-- Claim ownership of the original work
-
-๐ **You MUST:**
-- Include the Apache 2.0 License
-- Include copyright notice
-- State significant changes made
-- Include the NOTICE file (if applicable)
-
-For the complete license text, see [LICENSE](LICENSE) or visit [Apache.org](http://www.apache.org/licenses/LICENSE-2.0).
-
-## ๐ง Contact
-
-**Pasindu OG**
-- ๐ง Email: pasinduogdev@gmail.com
-- ๐ GitHub: [@pasinduog](https://github.com/pasinduog)
-- ๐ป Repository: [github.com/OG4Dev/og4dev-spring-response](https://github.com/OG4Dev/og4dev-spring-response)
-
-## ๐ Sponsorship
-
-**Love this project? Consider sponsoring!** โ
-
-This library is developed and maintained in my free time with passion for the Spring Boot community. Your sponsorship helps keep this project alive and growing!
-
-### ๐ Why Sponsor?
-
-Your support enables:
-- ๐ง **Continuous Maintenance** - Regular updates, bug fixes, and security patches
-- โจ **New Features** - Pagination, WebFlux support, internationalization, and more
-- ๐ **Better Documentation** - More examples, tutorials, and comprehensive guides
-- ๐งช **Higher Quality** - More tests, better coverage, and reliability improvements
-- โก **Faster Support** - Priority response to your issues and feature requests
-
-### ๐ How to Support
-
-Choose your preferred platform:
-
-- **GitHub Sponsors** - [Sponsor @pasinduog](https://github.com/sponsors/pasinduog)
-- **Buy Me a Coffee** - [buymeacoffee.com/pasinduog](https://buymeacoffee.com/pasinduog)
-- **PayPal** - [paypal.me/pasinduog](https://paypal.me/pasinduog)
-
-### ๐ Sponsor Perks
-
-Monthly sponsors receive:
-- ๐ Recognition in README with name/company logo
-- ๐ฏ Priority support for issues and features
-- ๐ข Early access to new releases
-- ๐ฌ Direct communication channel
-- ๐บ๏ธ Influence on project roadmap
-
-### ๐ Current Sponsors
-
-*Your name could be here! Become the first sponsor of OG4Dev Spring API Response.*
-
-
-
-**Every dollar helps!** Even small contributions make a big difference. Thank you for your support! โค๏ธ
-
-## ๐ Acknowledgments
-
-- Spring Boot team for the excellent framework
-- The open-source community for inspiration and support
-
-## ๐ Version History
-
-### 1.3.0 (February 2026) - Current Release
-
-โจ **Major Breaking Change: Opt-in Security Model**
-- **Security Philosophy Change** - Complete redesign from automatic to opt-in approach
- - **Before (v1.2.0):** All strings automatically trimmed and XSS-validated
- - **After (v1.3.0):** Fields preserved as-is by default; features enabled via annotations
- - **Reason:** Gives developers full control over which fields need processing
- - **Migration:** Add `@AutoTrim` and/or `@XssCheck` to fields that need them
-
-โจ **New Features:**
-- **@AutoTrim Annotation** - Opt-in automatic string trimming
- - Replaces default trimming behavior from v1.2.0
- - Apply to individual fields that should have whitespace removed
- - Examples: usernames, emails, search queries, reference numbers
- - Comprehensive Javadoc with 100+ lines of documentation
- - Null-safe processing with proper empty string handling
-- **@XssCheck Annotation** - Opt-in XSS validation
- - Replaces automatic XSS validation from v1.2.0
- - Apply to individual fields that should reject HTML tags
- - Examples: comments, user-generated content, profile fields
- - Fail-fast regex pattern: `(?s).*<\s*[a-zA-Z/!].*`
- - Comprehensive Javadoc with 150+ lines of documentation
-- **Fine-Grained Control** - Four processing modes per field
- - No annotations: String preserved as-is (default)
- - `@AutoTrim` only: String is trimmed
- - `@XssCheck` only: String is XSS-validated
- - Both annotations: String is trimmed then XSS-validated
-
-๐ **Documentation Overhaul:**
-- **Complete README Rewrite** - Entirely new documentation structure
- - Updated all code examples to show annotation-based approach
- - Added "Security Philosophy: Opt-in by Default" section
- - Expanded annotation usage examples with 20+ code samples
- - Clear migration guide from v1.2.0
- - Updated "How It Works" section with new implementation
-- **Comprehensive JavaDoc Updates**
- - `@AutoTrim`: 100+ lines of detailed documentation
- - `@XssCheck`: 150+ lines of detailed documentation
- - Updated `ApiResponseAutoConfiguration` to reflect opt-in model
- - All examples updated to show annotation usage
- - Zero Javadoc warnings
-
-๐ง **Technical Updates:**
-- Spring Boot 4.0.3 compatibility maintained
-- Jackson 3.x with tools.jackson packages
-- Enhanced `AdvancedStringDeserializer` with four-mode operation
-- Context-aware deserialization via `createContextual()`
-- Performance optimized: deserializer created once per field at initialization
-- Removed `@NoTrim` annotation (no longer needed with opt-in approach)
-
-๐ **Breaking Changes:**
-- **โ ๏ธ IMPORTANT:** Default behavior changed from v1.2.0
- - **v1.2.0:** All strings automatically trimmed and XSS-validated
- - **v1.3.0:** Strings preserved as-is unless annotated
- - **Migration Required:** Add `@AutoTrim` and/or `@XssCheck` to fields that need processing
- - **Example Migration:**
- ```java
- // v1.2.0 (automatic processing)
- public class UserDTO {
- private String username; // Was automatically trimmed
- private String comment; // Was automatically XSS-validated
- }
-
- // v1.3.0 (opt-in with annotations)
- public class UserDTO {
- @AutoTrim
- private String username; // Now explicitly trimmed
-
- @XssCheck
- private String comment; // Now explicitly XSS-validated
- }
- ```
-
-### 1.2.0 (February 2026) - Previous Release
-
-โจ **New Features:**
-- **@NoTrim Annotation** - Fine-grained control over string trimming behavior
- - Opt-out of automatic whitespace trimming for specific fields
- - Preserves whitespace for passwords, code snippets, Base64 data, API tokens
- - XSS validation still applies even with `@NoTrim` for security
- - Context-aware deserialization using `createContextual()` method
-- **Enhanced Security Model** - Improved XSS prevention approach
- - Changed from HTML escaping to fail-fast tag rejection
- - Regex pattern: `(?s).*<\s*[a-zA-Z/!].*` with DOTALL mode support
- - Prevents stored XSS, DOM-based XSS, and second-order injections
-- **Advanced String Deserializer** - Context-aware string processing
- - Two-mode operation: Default Mode (trim enabled) and NoTrim Mode (preserve whitespace)
- - Field-level annotation detection via `createContextual()`
- - Performance optimized: deserializer created once at initialization
-
-๐ **Documentation Enhancements:**
-- Comprehensive JavaDoc improvements
-- Updated `strictJsonCustomizer()` with ~280 lines of detailed documentation
-- Added `@NoTrim` annotation documentation with complete examples
-- Complete README refresh with corrected XSS prevention descriptions
-
-๐ง **Technical Updates:**
-- Spring Boot 4.0.3 compatibility (tested and verified)
-- Jackson 3.x with tools.jackson packages
-- Added annotation package: `io.github.og4dev.annotation`
-- Enhanced StdScalarDeserializer implementation
-
-๐ **Breaking Changes:**
-- None - Fully backward compatible with v1.1.0 and v1.1.1
-
-### 1.1.1 (February 2026)
-
-๐ง **Maintenance Release:**
-- **Spring Boot 4.0.3 Support** - Updated compatibility to Spring Boot 4.0.3
- - Tested and verified with Spring Boot 4.0.3
- - All features continue to work flawlessly
- - Seamless upgrade from Spring Boot 4.0.2
-- **Enhanced Stability** - Benefits from Spring Boot 4.0.3 bug fixes and improvements
-
-*Note: Version 1.1.1 was a Spring Boot patch compatibility update. New features were added in v1.2.0.*
-
-### 1.1.0 (February 2026)
-
-โ
**New Features:**
-- **Built-in Security Features** - Added `strictJsonCustomizer()` method with comprehensive JSON security
- - โ
**Strict Property Validation** - Rejects unknown properties to prevent mass assignment attacks
- - โ
**XSS Prevention** - Automatic HTML tag detection and rejection using regex pattern `.*<\s*[a-zA-Z/!].*`
- - โ
**String Sanitization** - Automatic whitespace trimming on deserialization
- - โ
**Case-Insensitive Enums** - Flexible enum handling for better API usability
-- **Comprehensive Exception Handling** - GlobalExceptionHandler with 10 built-in exception handlers
- - General exceptions (HTTP 500)
- - Validation errors with field-level details (HTTP 400)
- - Type mismatch errors (HTTP 400)
- - Malformed JSON requests (HTTP 400)
- - Missing required parameters (HTTP 400)
- - 404 Not Found errors
- - 405 Method Not Allowed
- - 415 Unsupported Media Type
- - Null pointer exceptions (HTTP 500)
- - Custom ApiException instances (custom status codes)
-- **Distributed Tracing Support** - Trace IDs in error responses for debugging
-- **RFC 9457 Compliance** - Standard ProblemDetail format for error responses
-- **Spring Boot Auto-Configuration** - Zero-configuration setup via META-INF imports
-- **TraceIdFilter** - Optional servlet filter for trace ID generation and MDC integration
-- **Custom Business Exceptions** - Abstract ApiException class for domain-specific errors
-
-๐ง **Improvements:**
-- **Complete Javadoc Coverage** - 100% documentation with zero warnings
- - Comprehensive documentation for all classes and methods
- - Detailed security feature explanations
- - Code examples for all major features
- - Complete @param, @return, @see, and @since tags
-- **Enhanced Security** - Fail-fast approach with HTML tag rejection instead of escaping
-- **Better Error Messages** - Clear, actionable error messages for all scenarios
-- **Improved Logging** - Consistent trace IDs between logs and error responses
-- **Production-Ready Quality** - Clean Maven builds, proper documentation, battle-tested code
-
-๐ **Documentation:**
-- Added dedicated "Built-in Security Features" section in README
-- Enhanced JavaDoc for `strictJsonCustomizer()` method with detailed examples
-- Added comprehensive exception handling documentation
-- Added distributed tracing examples and best practices
-- Complete package-info.java files for all packages
-
-๐ง **Technical Updates:**
-- Java 17+ support (Java 21 LTS recommended)
-- Spring Boot 3.2.0 - 4.0.3 compatibility (tested with 4.0.3)
-- Jackson 3.x compatibility (tools.jackson packages)
-- Pure Java implementation - No Lombok dependency
-- Zero external runtime dependencies
-- Ultra-lightweight: ~10KB JAR size
-
-### 1.0.0 (February 2026) - Initial Release
-
-โ
**Core Features:**
-- Standard API Response wrapper with generic type support
-- Factory methods: `success()`, `created()`, `status()`
-- Automatic RFC 3339 UTC timestamp generation
-- Thread-safe & immutable design
-- Full Spring Boot 3.2.0+ integration
-- Comprehensive JavaDoc documentation
-
-๐ฏ **Roadmap:**
-- Spring WebFlux support (reactive)
-- Pagination metadata support
-- OpenAPI schema generation
-- Internationalization (i18n) support
-- Response compression support
-- Custom serialization options
-- Metrics and monitoring integration
-
----
-
-## ๐ Summary
-
-The **OG4Dev Spring API Response** library is a production-ready, zero-configuration solution for standardizing REST API responses in Spring Boot applications.
-
-### ๐ฏ Why Choose This Library?
-
-โ
**Instant Setup** - Add dependency, start using. No configuration needed.
-โ
**Battle-Tested** - Production-ready code with comprehensive testing
-โ
**Modern Standards** - RFC 9457 ProblemDetail, Spring Boot 4.x support
-โ
**Developer Friendly** - Comprehensive docs, clear examples, active maintenance
-โ
**Lightweight** - Only ~10KB, zero runtime dependencies
-โ
**Type Safe** - Full generic support with compile-time checking
-โ
**Pure Java** - No Lombok or external dependencies required
-
-### ๐ Quick Stats
-
-| Metric | Value |
-|--------|-------|
-| JAR Size | ~10KB |
-| Response Time | < 1ms |
-| Memory per Response | ~200 bytes |
-| Thread Safety | 100% |
-| Spring Boot Support | 3.2.0 - 4.0.3 |
-| Java Version | 17+ |
-
-### ๐ Quick Access
-
-- ๐ฆ **[Maven Central](https://central.sonatype.com/artifact/io.github.og4dev/og4dev-spring-response)** - Download & integration
-- ๐ **[JavaDoc](https://javadoc.io/doc/io.github.og4dev/og4dev-spring-response)** - Complete API documentation
-- ๐ **[Issues](https://github.com/OG4Dev/og4dev-spring-response/issues)** - Report bugs or request features
-- ๐ฌ **[Discussions](https://github.com/OG4Dev/og4dev-spring-response/discussions)** - Ask questions & share ideas
+**Pasindu OG** | [pasinduogdev@gmail.com](mailto:pasinduogdev@gmail.com) |
+GitHub: [@pasinduog](https://github.com/pasinduog)
---
-**โญ If you find this library helpful, please give it a star on GitHub!**
-
-Made with โค๏ธ by [Pasindu OG](https://github.com/pasinduog)
+**โญ If you find this library helpful, please give it a star on GitHub!**
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index e5fdb00..8d66f6c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
io.github.og4dev
og4dev-spring-response
- 1.3.0
+ 1.4.0
OG4Dev Spring API Response
A lightweight, zero-configuration REST API Response wrapper and Global Exception Handler (RFC 9457) for Spring Boot applications, maintained by OG4Dev.
diff --git a/src/main/java/io/github/og4dev/advice/GlobalResponseWrapper.java b/src/main/java/io/github/og4dev/advice/GlobalResponseWrapper.java
index ed8deb0..fb694e3 100644
--- a/src/main/java/io/github/og4dev/advice/GlobalResponseWrapper.java
+++ b/src/main/java/io/github/og4dev/advice/GlobalResponseWrapper.java
@@ -2,6 +2,7 @@
import io.github.og4dev.annotation.AutoResponse;
import io.github.og4dev.dto.ApiResponse;
+import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
@@ -44,7 +45,7 @@
*/
@RestControllerAdvice(annotations = AutoResponse.class)
@SuppressWarnings("unused")
-public class GlobalResponseWrapper implements ResponseBodyAdvice {
+public @NullMarked class GlobalResponseWrapper implements ResponseBodyAdvice {
/**
* Determines whether the current response should be intercepted and wrapped.
@@ -65,7 +66,7 @@ public class GlobalResponseWrapper implements ResponseBodyAdvice {
* @return {@code true} if the body should be wrapped; {@code false} if it should be skipped.
*/
@Override
- public boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType) {
+ public @NullMarked boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType) {
Class> type = returnType.getParameterType();
return !ApiResponse.class.isAssignableFrom(type) &&
!ResponseEntity.class.isAssignableFrom(type) &&
diff --git a/src/main/java/io/github/og4dev/advice/package-info.java b/src/main/java/io/github/og4dev/advice/package-info.java
new file mode 100644
index 0000000..793e9b3
--- /dev/null
+++ b/src/main/java/io/github/og4dev/advice/package-info.java
@@ -0,0 +1,30 @@
+/**
+ * Provides global advisory components for the OG4Dev Spring API Response Library.
+ *
+ * This package contains Spring {@link org.springframework.web.bind.annotation.RestControllerAdvice}
+ * implementations that intercept and modify HTTP responses and requests globally across the application.
+ *
+ *
+ * The primary component within this package is the {@link io.github.og4dev.advice.GlobalResponseWrapper}.
+ * It facilitates the seamless, opt-in encapsulation of standard controller return values into the
+ * standardized {@link io.github.og4dev.dto.ApiResponse} format, significantly reducing boilerplate code.
+ *
+ * Integration & Usage
+ *
+ * Components in this package are automatically registered via Spring Boot's auto-configuration
+ * mechanism ({@code ApiResponseAutoConfiguration}). Developers do not need to manually scan, import,
+ * or configure this package.
+ *
+ *
+ * To activate the response wrapping capabilities provided by this package, simply annotate
+ * target REST controllers or specific methods with the {@link io.github.og4dev.annotation.AutoResponse @AutoResponse}
+ * annotation.
+ *
+ *
+ * @author Pasindu OG
+ * @version 1.4.0
+ * @see io.github.og4dev.advice.GlobalResponseWrapper
+ * @see io.github.og4dev.annotation.AutoResponse
+ * @since 1.4.0
+ */
+package io.github.og4dev.advice;
\ No newline at end of file
diff --git a/src/main/java/io/github/og4dev/annotation/AutoResponse.java b/src/main/java/io/github/og4dev/annotation/AutoResponse.java
index d22eb84..3360904 100644
--- a/src/main/java/io/github/og4dev/annotation/AutoResponse.java
+++ b/src/main/java/io/github/og4dev/annotation/AutoResponse.java
@@ -2,8 +2,56 @@
import java.lang.annotation.*;
+/**
+ * Opt-in annotation to enable automatic API response wrapping for Spring REST controllers.
+ *
+ * When this annotation is applied to a {@link org.springframework.web.bind.annotation.RestController}
+ * class or a specific request mapping method, the {@link io.github.og4dev.advice.GlobalResponseWrapper}
+ * intercepts the returned object and automatically encapsulates it within the standardized
+ * {@link io.github.og4dev.dto.ApiResponse} format.
+ *
+ * Usage:
+ *
+ * Class Level ({@link ElementType#TYPE}): Applies the wrapping behavior to all endpoint methods within the controller.
+ * Method Level ({@link ElementType#METHOD}): Applies the wrapping behavior only to the specific annotated method.
+ *
+ * Example:
+ * {@code
+ * @RestController
+ * @RequestMapping("/api/users")
+ * @AutoResponse // All methods in this controller will be automatically wrapped
+ * public class UserController {
+ * * @GetMapping("/{id}")
+ * public UserDto getUser(@PathVariable Long id) {
+ * // Returns: { "status": "Success", "content": { "id": 1, ... }, "timestamp": "..." }
+ * return userService.findById(id);
+ * }
+ * * @PostMapping
+ * @ResponseStatus(HttpStatus.CREATED)
+ * // @AutoResponse can also be placed here for method-level granularity instead of class-level
+ * public UserDto createUser(@RequestBody UserDto dto) {
+ * return userService.create(dto);
+ * }
+ * }
+ * }
+ * Exclusions:
+ *
+ * To prevent errors and double-wrapping, the interceptor will safely ignore methods that return:
+ *
+ *
+ * {@code ApiResponse} or {@code ResponseEntity} (Assumes the developer has explicitly formatted the response)
+ * {@code ProblemDetail} (RFC 9457 error responses managed by the global exception handler)
+ * {@code String} (Bypassed to avoid {@code ClassCastException} with Spring's internal string message converters)
+ *
+ *
+ * @author Pasindu OG
+ * @version 1.4.0
+ * @see io.github.og4dev.advice.GlobalResponseWrapper
+ * @see io.github.og4dev.dto.ApiResponse
+ * @since 1.4.0
+ */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoResponse {
-}
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/og4dev/annotation/AutoTrim.java b/src/main/java/io/github/og4dev/annotation/AutoTrim.java
index 05ae376..cb70fce 100644
--- a/src/main/java/io/github/og4dev/annotation/AutoTrim.java
+++ b/src/main/java/io/github/og4dev/annotation/AutoTrim.java
@@ -110,7 +110,7 @@
*
*
* @author Pasindu OG
- * @version 1.3.0
+ * @version 1.4.0
* @see io.github.og4dev.config.ApiResponseAutoConfiguration#strictJsonCustomizer()
* @see io.github.og4dev.annotation.XssCheck
* @see tools.jackson.databind.ValueDeserializer#createContextual
diff --git a/src/main/java/io/github/og4dev/annotation/XssCheck.java b/src/main/java/io/github/og4dev/annotation/XssCheck.java
index 9ded2d5..f009a8e 100644
--- a/src/main/java/io/github/og4dev/annotation/XssCheck.java
+++ b/src/main/java/io/github/og4dev/annotation/XssCheck.java
@@ -201,7 +201,7 @@
*
*
* @author Pasindu OG
- * @version 1.3.0
+ * @version 1.4.0
* @see io.github.og4dev.config.ApiResponseAutoConfiguration#strictJsonCustomizer()
* @see io.github.og4dev.annotation.AutoTrim
* @see tools.jackson.databind.ValueDeserializer#createContextual
diff --git a/src/main/java/io/github/og4dev/annotation/package-info.java b/src/main/java/io/github/og4dev/annotation/package-info.java
index b9c8ae7..cbc9df8 100644
--- a/src/main/java/io/github/og4dev/annotation/package-info.java
+++ b/src/main/java/io/github/og4dev/annotation/package-info.java
@@ -152,7 +152,7 @@
*
*
* @author Pasindu OG
- * @version 1.3.0
+ * @version 1.4.0
* @see io.github.og4dev.annotation.AutoTrim
* @see io.github.og4dev.annotation.XssCheck
* @see io.github.og4dev.config.ApiResponseAutoConfiguration
diff --git a/src/main/java/io/github/og4dev/config/ApiResponseAutoConfiguration.java b/src/main/java/io/github/og4dev/config/ApiResponseAutoConfiguration.java
index aa4abe6..240b0e1 100644
--- a/src/main/java/io/github/og4dev/config/ApiResponseAutoConfiguration.java
+++ b/src/main/java/io/github/og4dev/config/ApiResponseAutoConfiguration.java
@@ -1,8 +1,11 @@
package io.github.og4dev.config;
+import io.github.og4dev.advice.GlobalResponseWrapper;
+import io.github.og4dev.annotation.AutoResponse;
import io.github.og4dev.annotation.AutoTrim;
import io.github.og4dev.annotation.XssCheck;
import io.github.og4dev.exception.GlobalExceptionHandler;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -17,7 +20,8 @@
*
* This configuration is automatically loaded by Spring Boot's autoconfiguration mechanism
* when the library is present on the classpath. It registers essential beans required for
- * the library to function properly, including the comprehensive global exception handler.
+ * the library to function properly, including the comprehensive global exception handler
+ * and the automatic response wrapper.
*
*
* Zero Configuration Required: Simply adding the library dependency enables all features
@@ -25,14 +29,15 @@
*
* What Gets Auto-Configured:
*
- * {@link GlobalExceptionHandler} - Comprehensive exception handling with RFC 9457 ProblemDetail format
- *
- * 10 built-in exception handlers covering all common error scenarios
- * Automatic trace ID generation and logging
- * Validation error aggregation and formatting
- * Production-ready error messages
- *
- *
+ * {@link GlobalExceptionHandler} - Comprehensive exception handling with RFC 9457 ProblemDetail format
+ *
+ * 10 built-in exception handlers covering all common error scenarios
+ * Automatic trace ID generation and logging
+ * Validation error aggregation and formatting
+ * Production-ready error messages
+ *
+ *
+ * {@link GlobalResponseWrapper} - Automatic wrapping of controller responses (Opt-in via {@code @AutoResponse})
*
* How It Works:
*
@@ -46,9 +51,9 @@
*
{@code
* @SpringBootApplication(exclude = ApiResponseAutoConfiguration.class)
* public class Application {
- * public static void main(String[] args) {
- * SpringApplication.run(Application.class, args);
- * }
+ * public static void main(String[] args) {
+ * SpringApplication.run(Application.class, args);
+ * }
* }
* }
*
@@ -57,16 +62,11 @@
*
* spring.autoconfigure.exclude=io.github.og4dev.config.ApiResponseAutoConfiguration
*
- *
- * Alternatively, disable the exception handler while keeping other library features:
- *
- *
- * api-response.enabled=false
- *
*
* @author Pasindu OG
- * @version 1.3.0
+ * @version 1.4.0
* @see GlobalExceptionHandler
+ * @see GlobalResponseWrapper
* @see org.springframework.boot.autoconfigure.AutoConfiguration
* @since 1.0.0
*/
@@ -76,10 +76,6 @@ public class ApiResponseAutoConfiguration {
/**
* Default constructor for ApiResponseAutoConfiguration.
- *
- * This constructor is automatically invoked by Spring's dependency injection
- * container during application startup when autoconfiguration is enabled.
- *
*/
public ApiResponseAutoConfiguration() {
// Default constructor for Spring autoconfiguration
@@ -87,42 +83,66 @@ public ApiResponseAutoConfiguration() {
/**
* Registers the {@link GlobalExceptionHandler} as a Spring bean for automatic exception handling.
+ * * // ... (เถดเถปเถซ JavaDoc เถเถเถธเถบเท, เทเทเถฑเทเท เทเทเถฑเทเถฑเท เถฑเท) ...
+ * * @return A new instance of {@link GlobalExceptionHandler} registered as a Spring bean.
+ */
+ @Bean
+ public GlobalExceptionHandler apiResponseAdvisor() {
+ return new GlobalExceptionHandler();
+ }
+
+ /**
+ * Registers the {@link GlobalResponseWrapper} as a Spring bean for automatic API response wrapping.
*
- * The handler provides comprehensive centralized exception management using Spring's
- * {@link org.springframework.web.bind.annotation.RestControllerAdvice} mechanism,
- * automatically converting 10 different types of exceptions to RFC 9457 ProblemDetail responses.
- *
- *
- * Exception Coverage:
+ * This bean enables the opt-in {@link AutoResponse @AutoResponse} feature. When a REST controller
+ * or method is annotated with {@code @AutoResponse}, this wrapper automatically intercepts the
+ * outgoing payload and encapsulates it within the standardized {@code ApiResponse} structure
+ * before it is written to the HTTP response body.
*
+ * Key Capabilities:
*
- * General exceptions (500 Internal Server Error)
- * Validation errors with field-level details (400 Bad Request)
- * Type mismatch errors (400 Bad Request)
- * Malformed JSON requests (400 Bad Request)
- * Missing required parameters (400 Bad Request)
- * 404 Not Found errors
- * 405 Method Not Allowed
- * 415 Unsupported Media Type
- * Null pointer exceptions (500 Internal Server Error)
- * Custom ApiException instances (custom status codes)
+ * Zero Boilerplate: Eliminates the need to manually return {@code ResponseEntity>}
+ * from every controller method.
+ * Status Code Preservation: Intelligently reads and preserves custom HTTP status codes
+ * set via {@code @ResponseStatus} (e.g., 201 Created).
+ * Double-Wrap Prevention: Safely skips wrapping if the controller already returns
+ * an {@code ApiResponse} or {@code ResponseEntity}.
+ * Error Compatibility: Bypasses {@code ProblemDetail} and exception responses to maintain
+ * RFC 9457 compliance managed by {@link GlobalExceptionHandler}.
*
+ * Example Usage:
+ * {@code
+ * @RestController
+ * @RequestMapping("/api/users")
+ * @AutoResponse // Enables automatic wrapping for all methods in this controller
+ * public class UserController {
+ * * @GetMapping("/{id}")
+ * public UserDto getUser(@PathVariable Long id) {
+ * // Simply return the DTO. It will be sent to the client as:
+ * // { "status": "Success", "content": { "id": 1, "name": "..." }, "timestamp": "..." }
+ * return userService.findById(id);
+ * }
+ * * @PostMapping
+ * @ResponseStatus(HttpStatus.CREATED)
+ * public UserDto createUser(@RequestBody UserDto dto) {
+ * // The 201 Created status will be preserved in the final ApiResponse
+ * return userService.create(dto);
+ * }
+ * }
+ * }
*
- * Features:
+ * Note: This bean is conditionally loaded using {@code @ConditionalOnMissingBean}, allowing developers
+ * to easily override the default wrapping behavior by defining their own {@code GlobalResponseWrapper} bean.
*
- *
- * Automatic trace ID generation for all errors
- * Consistent trace IDs between logs and responses
- * RFC 9457 compliant error format
- * Comprehensive SLF4J logging with appropriate severity levels
- * Automatic timestamp inclusion in all error responses
- *
- *
- * @return A new instance of {@link GlobalExceptionHandler} registered as a Spring bean.
+ * * @return A new instance of {@link GlobalResponseWrapper} registered as a Spring bean.
+ * @see AutoResponse
+ * @see io.github.og4dev.dto.ApiResponse
+ * @since 1.4.0
*/
@Bean
- public GlobalExceptionHandler apiResponseAdvisor() {
- return new GlobalExceptionHandler();
+ @ConditionalOnMissingBean // เถธเทเถ เถฏเทเถธเทเถธเทเถญเท เถ
เถฑเทเถญเท เถ
เถบเถง เถธเทเถ override เถเถปเถฑเทเถฑ เถดเทเท
เทเทเถฑเท
+ public GlobalResponseWrapper globalResponseWrapper() {
+ return new GlobalResponseWrapper();
}
/**
diff --git a/src/main/java/io/github/og4dev/config/package-info.java b/src/main/java/io/github/og4dev/config/package-info.java
index 4cd5972..3aedd22 100644
--- a/src/main/java/io/github/og4dev/config/package-info.java
+++ b/src/main/java/io/github/og4dev/config/package-info.java
@@ -1,7 +1,7 @@
/**
* Configuration classes for the OG4Dev Spring API Response Library.
*
- * This package contains Spring Boot auto-configuration classes that automatically
+ * This package contains Spring Boot autoconfiguration classes that automatically
* register required beans when the library is added to the classpath. Zero manual
* configuration is required - simply add the dependency and all features are enabled.
*
@@ -12,8 +12,7 @@
*
*
* @author Pasindu OG
- * @version 1.3.0
+ * @version 1.4.0
* @since 1.0.0
*/
-package io.github.og4dev.config;
-
+package io.github.og4dev.config;
\ No newline at end of file
diff --git a/src/main/java/io/github/og4dev/dto/ApiResponse.java b/src/main/java/io/github/og4dev/dto/ApiResponse.java
index 36d202f..0c81a03 100644
--- a/src/main/java/io/github/og4dev/dto/ApiResponse.java
+++ b/src/main/java/io/github/og4dev/dto/ApiResponse.java
@@ -49,7 +49,7 @@
*
* @param the type of the response content (can be any Java type or Void for no content)
* @author Pasindu OG
- * @version 1.3.0
+ * @version 1.4.0
* @since 1.0.0
* @see org.springframework.http.ResponseEntity
* @see org.springframework.http.HttpStatus
diff --git a/src/main/java/io/github/og4dev/dto/package-info.java b/src/main/java/io/github/og4dev/dto/package-info.java
index a733fe1..6c20b78 100644
--- a/src/main/java/io/github/og4dev/dto/package-info.java
+++ b/src/main/java/io/github/og4dev/dto/package-info.java
@@ -17,7 +17,7 @@
*
*
* @author Pasindu OG
- * @version 1.3.0
+ * @version 1.4.0
* @since 1.0.0
*/
package io.github.og4dev.dto;
\ No newline at end of file
diff --git a/src/main/java/io/github/og4dev/exception/ApiException.java b/src/main/java/io/github/og4dev/exception/ApiException.java
index 7f74a10..77cf7f4 100644
--- a/src/main/java/io/github/og4dev/exception/ApiException.java
+++ b/src/main/java/io/github/og4dev/exception/ApiException.java
@@ -38,7 +38,7 @@
*
*
* @author Pasindu OG
- * @version 1.3.0
+ * @version 1.4.0
* @since 1.0.0
* @see io.github.og4dev.exception.GlobalExceptionHandler
* @see org.springframework.http.HttpStatus
diff --git a/src/main/java/io/github/og4dev/exception/GlobalExceptionHandler.java b/src/main/java/io/github/og4dev/exception/GlobalExceptionHandler.java
index 5341b06..e2a3499 100644
--- a/src/main/java/io/github/og4dev/exception/GlobalExceptionHandler.java
+++ b/src/main/java/io/github/og4dev/exception/GlobalExceptionHandler.java
@@ -75,7 +75,7 @@
*
*
* @author Pasindu OG
- * @version 1.3.0
+ * @version 1.4.0
* @since 1.0.0
* @see org.springframework.web.bind.annotation.RestControllerAdvice
* @see org.springframework.http.ProblemDetail
diff --git a/src/main/java/io/github/og4dev/exception/package-info.java b/src/main/java/io/github/og4dev/exception/package-info.java
index 376efcf..6e81223 100644
--- a/src/main/java/io/github/og4dev/exception/package-info.java
+++ b/src/main/java/io/github/og4dev/exception/package-info.java
@@ -26,7 +26,7 @@
*
*
* @author Pasindu OG
- * @version 1.3.0
+ * @version 1.4.0
* @since 1.0.0
*/
package io.github.og4dev.exception;
diff --git a/src/main/java/io/github/og4dev/filter/TraceIdFilter.java b/src/main/java/io/github/og4dev/filter/TraceIdFilter.java
index 3c1b261..658d146 100644
--- a/src/main/java/io/github/og4dev/filter/TraceIdFilter.java
+++ b/src/main/java/io/github/og4dev/filter/TraceIdFilter.java
@@ -57,7 +57,7 @@
*
*
* @author Pasindu OG
- * @version 1.3.0
+ * @version 1.4.0
* @since 1.0.0
* @see org.springframework.web.filter.OncePerRequestFilter
* @see org.slf4j.MDC
diff --git a/src/main/java/io/github/og4dev/filter/package-info.java b/src/main/java/io/github/og4dev/filter/package-info.java
index 5ed5ef8..dcff3f0 100644
--- a/src/main/java/io/github/og4dev/filter/package-info.java
+++ b/src/main/java/io/github/og4dev/filter/package-info.java
@@ -21,7 +21,7 @@
*
*
* @author Pasindu OG
- * @version 1.3.0
+ * @version 1.4.0
* @since 1.0.0
*/
package io.github.og4dev.filter;
diff --git a/src/main/java/io/github/og4dev/package-info.java b/src/main/java/io/github/og4dev/package-info.java
index 543a2fc..a471b48 100644
--- a/src/main/java/io/github/og4dev/package-info.java
+++ b/src/main/java/io/github/og4dev/package-info.java
@@ -6,11 +6,11 @@
*
*
* Simply add the dependency to your project, and all features are automatically enabled through
- * Spring Boot auto-configuration.
+ * Spring Boot autoconfiguration.
*
*
* @author Pasindu OG
- * @version 1.3.0
+ * @version 1.4.0
* @since 1.0.0
*/
package io.github.og4dev;
From 15bf282d073a5dd24ae7dafbe123d1d2815599e2 Mon Sep 17 00:00:00 2001
From: Pasindu Owa Gamage
Date: Sun, 1 Mar 2026 03:05:10 +0530
Subject: [PATCH 4/5] fix: enable @AutoResponse annotation at the method level
Updated the `supports` method in `GlobalResponseWrapper` to evaluate the presence of the `@AutoResponse` annotation on both the declaring class and the specific handler method. Previously, method-level annotations were ignored. Also updated the corresponding Javadoc to reflect the new evaluation criteria.
---
README.md | 52 +++-
.../og4dev/advice/GlobalResponseWrapper.java | 68 ++++-
.../io/github/og4dev/annotation/AutoTrim.java | 71 ++---
.../io/github/og4dev/annotation/XssCheck.java | 180 ++++++------
.../config/ApiResponseAutoConfiguration.java | 275 ++++--------------
5 files changed, 282 insertions(+), 364 deletions(-)
diff --git a/README.md b/README.md
index 5b19353..58ba55a 100644
--- a/README.md
+++ b/README.md
@@ -105,7 +105,7 @@ A lightweight, type-safe API Response wrapper for Spring Boot applications. Stan
* ๐ **Spring Native** - Built on `ResponseEntity` and `HttpStatus`
* ๐ **RFC 9457 Compliance** - Standard ProblemDetail format (supersedes RFC 7807)
* ๐ **Complete JavaDoc** - Every class and method fully documented with comprehensive examples
-* ๐ **Opt-in Security Features** - Fine-grained JSON request protection via field annotations
+* ๐ **Opt-in Security Features** - Fine-grained JSON request protection via field and class-level annotations
* โ
**Strict JSON Validation** - Rejects unknown properties to prevent mass assignment attacks (automatic)
* โ
**XSS Prevention** - HTML tag detection and rejection via `@XssCheck` annotation (opt-in)
* โ
**Smart String Trimming** - Whitespace trimming via `@AutoTrim` annotation (opt-in)
@@ -125,8 +125,9 @@ Unlike other response wrapper libraries, this one offers:
* โ
**Native Spring Boot 3.x/4.x Auto-Configuration** - No manual setup required
* โ
**Zero-Boilerplate @AutoResponse** - Return raw objects, let the library wrap them automatically while preserving
your HTTP Status codes.
+* โ
**Intelligent String Handling** - Safely wraps raw `String` returns into JSON without throwing `ClassCastException`.
* โ
**RFC 9457 ProblemDetail Support** - Industry-standard error responses (latest RFC)
-* โ
**Opt-in Security Features** - Fine-grained control via field-level annotations (`@XssCheck`, `@AutoTrim`)
+* โ
**Opt-in Security Features** - Fine-grained control via field and **class-level** annotations (`@XssCheck`, `@AutoTrim`)
* โ
**Zero External Dependencies** - Pure Java implementation, won't conflict with your application
* โ
**Extensible Exception Handling** - Create custom business exceptions easily
* โ
**Trace ID Support** - Built-in distributed tracing capabilities
@@ -144,6 +145,7 @@ Unlike other response wrapper libraries, this one offers:
1.4.0
+
```
### Gradle (Latest - v1.4.0)
@@ -151,6 +153,7 @@ Unlike other response wrapper libraries, this one offers:
```gradle
implementation 'io.github.og4dev:og4dev-spring-response:1.4.0'
+
```
### Gradle Kotlin DSL (Latest - v1.4.0)
@@ -158,6 +161,7 @@ implementation 'io.github.og4dev:og4dev-spring-response:1.4.0'
```kotlin
implementation("io.github.og4dev:og4dev-spring-response:1.4.0")
+
```
---
@@ -184,6 +188,7 @@ io.github.og4dev
โโโ filter/
โโโ TraceIdFilter.java # Request trace ID generation
+
```
## ๐ฏ Quick Start
@@ -204,6 +209,7 @@ public class UserController {
}
}
+
```
### Method 2: Automatic Wrapping (New in v1.4.0) ๐
@@ -228,8 +234,15 @@ public class UserController {
public User createUser(@RequestBody UserDto dto) {
return userService.create(dto);
}
+
+ @GetMapping("/greeting")
+ public String greeting() {
+ // Raw strings are safely converted to JSON ApiResponse too!
+ return "Hello World";
+ }
}
+
```
**Both methods produce the exact same JSON:**
@@ -246,6 +259,7 @@ public class UserController {
"timestamp": "2026-02-28T10:30:45.123Z"
}
+
```
## โ๏ธ Auto-Configuration
@@ -277,6 +291,7 @@ public class ProductController {
// ...
}
+
```
### Key Capabilities:
@@ -285,13 +300,14 @@ public class ProductController {
Created) and reflects them in the `ApiResponse`.
* โ
**Double-Wrap Prevention:** Safely skips wrapping if you explicitly return an `ApiResponse` or `ResponseEntity`.
* โ
**Error Compatibility:** Bypasses `ProblemDetail` responses, ensuring standard error handling is never broken.
-* โ
**String Safety:** Skips raw `String` returns to prevent `ClassCastException` with Spring's internal string message
- converters.
+* โ
**Intelligent String Handling:** Uses the injected `ObjectMapper` to serialize raw `String` returns into JSON format safely, preventing `ClassCastException` conflicts with Spring's native `StringHttpMessageConverter`.
## ๐ Built-in Security Features
The library provides fine-grained security and data processing features through field-level annotations. By default, **fields are NOT modified** unless explicitly annotated.
+*New in v1.4.0:* You can now apply `@AutoTrim` and `@XssCheck` to **entire classes** to protect all string fields at once!
+
### 1. Strict Property Validation ๐ก๏ธ (Automatic)
Rejects JSON payloads containing unexpected fields to prevent mass assignment attacks.
@@ -305,6 +321,7 @@ Fail-fast HTML tag detection and rejection using regex pattern `(?s).*<\s*[a-zA-
@XssCheck
private String comment; // Rejects ""
+
```
### 3. Opt-in String Trimming with @AutoTrim โ๏ธ
@@ -316,6 +333,23 @@ Automatic whitespace removal for specific fields.
@AutoTrim
private String username; // " john_doe " -> "john_doe"
+
+```
+
+### 4. Class-Level Protection (New in v1.4.0) ๐ก๏ธ
+
+Apply annotations to the class level to automatically protect **ALL** string fields within that class!
+
+```java
+@AutoTrim
+@XssCheck
+public class SecureRegistrationDTO {
+ // Both of these fields will be automatically trimmed and XSS-validated!
+ private String username;
+ private String email;
+ private String bio;
+}
+
```
*(See full Security details in the Javadocs and examples above).*
@@ -336,6 +370,7 @@ public class ResourceNotFoundException extends ApiException {
}
}
+
```
## ๐ Real-World Examples
@@ -376,6 +411,7 @@ public class ProductController {
}
}
+
```
## ๐ API Reference
@@ -392,8 +428,12 @@ public class ProductController {
* Opt-in automatic response wrapping to eliminate boilerplate code.
* Returns raw DTOs from controllers and automatically wraps them in `ApiResponse`.
* Preserves HTTP status codes from `@ResponseStatus`.
-* Intelligently skips `ResponseEntity`, `ApiResponse`, `ProblemDetail`, and `String` to prevent double-wrapping and
- casting errors.
+* Intelligently skips `ResponseEntity`, `ApiResponse`, and `ProblemDetail` to prevent double-wrapping.
+* **Intelligent String Handling:** Uses Spring's `ObjectMapper` to safely serialize raw `String` returns to JSON, avoiding `ClassCastException` with native converters.
+
+
+* **Class-Level Security Annotations**
+* `@AutoTrim` and `@XssCheck` can now be applied at the Class level (`ElementType.TYPE`) to automatically protect all String fields within the DTO at once.
* **package-info.java documentation** added for the new `advice` package.
diff --git a/src/main/java/io/github/og4dev/advice/GlobalResponseWrapper.java b/src/main/java/io/github/og4dev/advice/GlobalResponseWrapper.java
index fb694e3..8d4a2f9 100644
--- a/src/main/java/io/github/og4dev/advice/GlobalResponseWrapper.java
+++ b/src/main/java/io/github/og4dev/advice/GlobalResponseWrapper.java
@@ -15,6 +15,8 @@
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.ObjectMapper;
/**
* Global response interceptor that automatically wraps REST controller outputs into the
@@ -32,6 +34,8 @@
* Status Code Preservation: Dynamically reads the current HTTP status of the response
* (e.g., set via {@code @ResponseStatus(HttpStatus.CREATED)}) and ensures it is accurately
* reflected in the final {@code ApiResponse}.
+ * String Payload Compatibility: Safely intercepts raw {@code String} returns and manually
+ * serializes them to prevent {@code ClassCastException} when Spring utilizes the {@code StringHttpMessageConverter}.
* Safety Mechanisms: Intelligently skips wrapping if the response is already formatted
* to prevent double-wrapping errors or interference with standard error handling protocols.
*
@@ -43,13 +47,32 @@
* @see ResponseBodyAdvice
* @since 1.4.0
*/
-@RestControllerAdvice(annotations = AutoResponse.class)
+@RestControllerAdvice
@SuppressWarnings("unused")
public @NullMarked class GlobalResponseWrapper implements ResponseBodyAdvice {
+ private final ObjectMapper objectMapper;
+
+ /**
+ * Constructs a new {@code GlobalResponseWrapper} with the provided {@link ObjectMapper}.
+ *
+ * @param objectMapper The Jackson object mapper used for explicit string serialization.
+ */
+ public GlobalResponseWrapper(ObjectMapper objectMapper) {
+ this.objectMapper = objectMapper;
+ }
+
/**
* Determines whether the current response should be intercepted and wrapped.
*
+ * This method evaluates two main conditions before allowing the response to be wrapped:
+ *
+ *
+ * Annotation Presence: The target controller class or the specific handler method
+ * must be annotated with {@link AutoResponse}.
+ * Type Exclusion: The return type must not be one of the explicitly excluded types.
+ *
+ *
* To guarantee application stability and adherence to standard HTTP protocols, this method
* specifically excludes the following return types from being wrapped:
*
@@ -57,21 +80,26 @@
* {@link ApiResponse} - Prevents recursive double-wrapping (e.g., {@code ApiResponse>}).
* {@link ResponseEntity} - Skips manual responses to respect developer's explicit configurations.
* {@link ProblemDetail} - Excludes RFC 9457 error responses generated by exception handlers.
- * {@link String} - Bypasses raw strings to prevent {@code ClassCastException} when Spring utilizes
- * the {@code StringHttpMessageConverter}.
*
+ *
+ * Note: Unlike standard wrappers, raw {@link String} payloads are supported and handled
+ * appropriately during the write phase.
+ *
*
* @param returnType The return type of the controller method.
* @param converterType The selected HTTP message converter.
- * @return {@code true} if the body should be wrapped; {@code false} if it should be skipped.
+ * @return {@code true} if annotated with {@code @AutoResponse} and not an excluded type; {@code false} otherwise.
*/
@Override
public @NullMarked boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType) {
Class> type = returnType.getParameterType();
- return !ApiResponse.class.isAssignableFrom(type) &&
- !ResponseEntity.class.isAssignableFrom(type) &&
- !ProblemDetail.class.isAssignableFrom(type) &&
- !String.class.isAssignableFrom(type);
+ boolean isExcludedType = ApiResponse.class.isAssignableFrom(type) ||
+ ResponseEntity.class.isAssignableFrom(type) ||
+ ProblemDetail.class.isAssignableFrom(type);
+ if (isExcludedType) return false;
+ boolean hasClassAnnotation = returnType.getDeclaringClass().isAnnotationPresent(AutoResponse.class);
+ boolean hasMethodAnnotation = returnType.hasMethodAnnotation(AutoResponse.class);
+ return hasClassAnnotation || hasMethodAnnotation;
}
/**
@@ -82,6 +110,12 @@
* Based on whether the status code represents a success (2xx) or another state, it dynamically
* assigns an appropriate message ("Success" or "Processed") to the API response.
*
+ *
+ * Special String Handling: If the intercepted payload is a raw {@code String}, it is
+ * explicitly serialized to a JSON string using the configured {@link ObjectMapper}, and the
+ * response {@code Content-Type} is strictly set to {@code application/json}. This prevents
+ * standard message converter conflicts.
+ *
*
* @param body The raw object returned by the controller method.
* @param returnType The return type of the controller method.
@@ -89,20 +123,28 @@
* @param selectedConverterType The selected HTTP message converter.
* @param request The current server HTTP request.
* @param response The current server HTTP response.
- * @return The newly wrapped {@code ApiResponse} object ready to be serialized.
+ * @return The newly wrapped {@code ApiResponse} object ready to be serialized, or a pre-serialized
+ * JSON {@code String} if the original payload was a raw string.
*/
@Override
public @Nullable Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class extends HttpMessageConverter>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
int statusCode = HttpStatus.OK.value();
- // Extract the dynamically set HTTP status code (e.g., from @ResponseStatus)
if (response instanceof ServletServerHttpResponse serverHttpResponse) {
statusCode = serverHttpResponse.getServletResponse().getStatus();
}
-
HttpStatus httpStatus = HttpStatus.valueOf(statusCode);
+ ApiResponse apiResponse = ApiResponse.status(httpStatus.is2xxSuccessful() ? "Success" : "Processed", body, httpStatus).getBody();
- // Wrap the payload and generate the final format
- return ApiResponse.status(httpStatus.is2xxSuccessful() ? "Success" : "Processed", body, httpStatus).getBody();
+ if (body instanceof String) {
+ try {
+ response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
+ assert apiResponse != null;
+ return objectMapper.writeValueAsString(apiResponse);
+ } catch (JacksonException e) {
+ return body;
+ }
+ }
+ return apiResponse;
}
}
\ No newline at end of file
diff --git a/src/main/java/io/github/og4dev/annotation/AutoTrim.java b/src/main/java/io/github/og4dev/annotation/AutoTrim.java
index cb70fce..768079e 100644
--- a/src/main/java/io/github/og4dev/annotation/AutoTrim.java
+++ b/src/main/java/io/github/og4dev/annotation/AutoTrim.java
@@ -9,57 +9,57 @@
* Annotation to explicitly enable automatic string trimming during JSON deserialization.
*
* By default, the OG4Dev Spring API Response library does NOT automatically trim strings.
- * This annotation allows you to opt-in to automatic trimming for specific fields where
- * removing leading and trailing whitespace is desired for data quality and consistency.
+ * This annotation allows you to opt-in to automatic trimming for specific fields or entire
+ * classes where removing leading and trailing whitespace is desired for data quality and consistency.
*
*
* Important: When {@code @AutoTrim} is applied, XSS validation (HTML tag detection)
* is still performed on the trimmed value to maintain security.
*
*
- * Use Cases
+ * Target Scopes
*
- * User input fields: Names, emails, addresses where whitespace is typically unwanted
- * Search queries: Remove accidental spaces from user search inputs
- * Usernames: Ensure consistent username formatting without leading/trailing spaces
- * Reference numbers: IDs, codes, or identifiers that should not have extra whitespace
- * Categories/Tags: Taxonomy values that need consistent formatting
+ * Field Level ({@link ElementType#FIELD}): Applies trimming only to the specific annotated String field.
+ * Class Level ({@link ElementType#TYPE}): Applies trimming to all String fields within the annotated class globally.
*
*
- * Example Usage
+ * Example Usage: Field Level
* {@code
* public class UserRegistrationDTO {
- * @AutoTrim
- * private String username; // Trimmed: " john_doe " โ "john_doe"
+ * @AutoTrim
+ * private String username; // Trimmed: " john_doe " โ "john_doe"
*
- * @AutoTrim
- * private String email; // Trimmed: " user@example.com " โ "user@example.com"
+ * @AutoTrim
+ * private String email; // Trimmed: " user@example.com " โ "user@example.com"
*
- * @AutoTrim
- * private String firstName; // Trimmed: " John " โ "John"
+ * private String password; // NOT trimmed (no annotation)
+ * private String bio; // NOT trimmed (no annotation)
+ * }
+ * }
*
- * private String password; // NOT trimmed (no annotation)
- * private String bio; // NOT trimmed (no annotation)
+ * Example Usage: Class Level
+ * {@code
+ * @AutoTrim // Automatically applies to ALL String fields in this class!
+ * public class GlobalTrimDTO {
+ * private String firstName; // Trimmed: " John " โ "John"
+ * private String lastName; // Trimmed: " Doe " โ "Doe"
+ * private String address; // Trimmed: " 123 Main St " โ "123 Main St"
* }
* }
*
- * Input/Output Examples
+ * Input/Output Examples (Class Level)
* {@code
- * // Request JSON
+ * // Request JSON for GlobalTrimDTO
* {
- * "username": " john_doe ",
- * "email": " john@example.com ",
- * "firstName": "\t\nJohn\t\n",
- * "password": " myPass123 ",
- * "bio": " Software Developer "
+ * "firstName": "\t\nJohn\t\n",
+ * "lastName": " Doe ",
+ * "address": " 123 Main St "
* }
*
* // After Deserialization
- * username = "john_doe" // โ Trimmed (has @AutoTrim)
- * email = "john@example.com" // โ Trimmed (has @AutoTrim)
- * firstName = "John" // โ Trimmed (has @AutoTrim)
- * password = " myPass123 " // โ NOT trimmed (no annotation)
- * bio = " Software Developer " // โ NOT trimmed (no annotation)
+ * firstName = "John" // โ Trimmed (due to class-level @AutoTrim)
+ * lastName = "Doe" // โ Trimmed (due to class-level @AutoTrim)
+ * address = "123 Main St" // โ Trimmed (due to class-level @AutoTrim)
* }
*
* XSS Validation Still Active
@@ -77,10 +77,10 @@
* You can combine {@code @AutoTrim} with {@link XssCheck @XssCheck} for both behaviors:
*
* {@code
+ * @AutoTrim // Trims all fields
* public class SecureDTO {
- * @AutoTrim
- * @XssCheck
- * private String cleanInput; // Both trimmed and XSS-validated
+ * @XssCheck
+ * private String cleanInput; // Both trimmed (from class scope) and XSS-validated
* }
* }
*
@@ -89,7 +89,8 @@
* This annotation is processed by the {@code AdvancedStringDeserializer} in
* {@link io.github.og4dev.config.ApiResponseAutoConfiguration#strictJsonCustomizer()}.
* The deserializer uses {@link tools.jackson.databind.ValueDeserializer#createContextual}
- * to detect the annotation and create a specialized instance that enables trimming.
+ * to detect the annotation on either the field itself or its declaring class, creating a
+ * specialized instance that enables trimming.
*
*
* Null Value Handling
@@ -116,8 +117,8 @@
* @see tools.jackson.databind.ValueDeserializer#createContextual
* @since 1.3.0
*/
-@Target(ElementType.FIELD)
+@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@SuppressWarnings("unused")
public @interface AutoTrim {
-}
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/og4dev/annotation/XssCheck.java b/src/main/java/io/github/og4dev/annotation/XssCheck.java
index f009a8e..aadffea 100644
--- a/src/main/java/io/github/og4dev/annotation/XssCheck.java
+++ b/src/main/java/io/github/og4dev/annotation/XssCheck.java
@@ -10,34 +10,41 @@
*
* By default, the OG4Dev Spring API Response library does NOT perform XSS validation on strings.
* This annotation allows you to opt-in to automatic HTML/XML tag detection and rejection for specific fields
- * where preventing malicious content injection is critical for security.
+ * or entire classes where preventing malicious content injection is critical for security.
*
*
* Security Approach: This annotation implements a fail-fast rejection strategy - requests
- * containing HTML tags are rejected entirely with a 400 Bad Request error. This is more secure than
+ * containing HTML tags are rejected entirely with a 400 Bad Request error. This is more secure than
* HTML escaping, as it prevents stored XSS, DOM-based XSS, and second-order injection vulnerabilities.
*
*
- * Use Cases
+ * Target Scopes
*
- * User-generated content: Comments, reviews, forum posts, chat messages
- * Profile information: Usernames, display names, bio fields
- * Search queries: User input that will be displayed or processed
- * Form inputs: Any field that accepts free-form text from users
- * API parameters: String parameters that should not contain markup
+ * Field Level ({@link ElementType#FIELD}): Applies XSS validation only to the specific annotated String field.
+ * Class Level ({@link ElementType#TYPE}): Applies XSS validation to all String fields within the annotated class globally.
*
*
- * Example Usage
+ * Example Usage: Field Level
* {@code
* public class CommentDTO {
- * @XssCheck
- * private String content; // XSS validated - rejects HTML tags
+ * @XssCheck
+ * private String content; // XSS validated - rejects HTML tags
*
- * @XssCheck
- * private String authorName; // XSS validated - rejects HTML tags
+ * @XssCheck
+ * private String authorName; // XSS validated - rejects HTML tags
*
- * private String commentId; // NOT validated (no annotation)
- * private Instant timestamp; // NOT validated (not a string)
+ * private String commentId; // NOT validated (no annotation)
+ * private Instant timestamp; // NOT validated (not a string)
+ * }
+ * }
+ *
+ * Example Usage: Class Level
+ * {@code
+ * @XssCheck // Automatically protects ALL String fields in this class!
+ * public class SecureUserProfileDTO {
+ * private String bio; // XSS validated automatically
+ * private String displayName; // XSS validated automatically
+ * private String websiteUrl; // XSS validated automatically
* }
* }
*
@@ -53,7 +60,7 @@
* {"content": ""} // Script injection
* {"content": " "} // Image XSS attack
* {"content": "Hello World"} // HTML break tag
- * {"content": ""} // HTML comment
+ * {"content": ""} // HTML comment
* {"content": ""} // DOCTYPE declaration
* {"content": ""} // Closing tag
* {"content": "Bold text "} // HTML formatting
@@ -65,12 +72,12 @@
*
* {@code
* {
- * "type": "about:blank",
- * "title": "Bad Request",
- * "status": 400,
- * "detail": "Security Error: HTML tags or XSS payloads are not allowed in the request.",
- * "traceId": "550e8400-e29b-41d4-a716-446655440000",
- * "timestamp": "2026-02-21T10:30:45.123Z"
+ * "type": "about:blank",
+ * "title": "Bad Request",
+ * "status": 400,
+ * "detail": "Security Error: HTML tags or XSS payloads are not allowed in the request.",
+ * "traceId": "550e8400-e29b-41d4-a716-446655440000",
+ * "timestamp": "2026-02-21T10:30:45.123Z"
* }
* }
*
@@ -82,20 +89,20 @@
* This pattern detects:
*
*
- * Opening tags: {@code }, {@code