}, {@code

}
- *
Multiline tags: Tags spanning multiple lines (DOTALL mode enabled)
+ *
Multiline tags: Tags spanning multiple lines (DOT ALL mode enabled)
*
*
* What is NOT detected (safe to use):
@@ -201,7 +201,7 @@
*
*
* @author Pasindu OG
- * @version 1.4.0
+ * @version 1.5.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 cbc9df8..9e79781 100644
--- a/src/main/java/io/github/og4dev/annotation/package-info.java
+++ b/src/main/java/io/github/og4dev/annotation/package-info.java
@@ -152,12 +152,10 @@
*
*
* @author Pasindu OG
- * @version 1.4.0
+ * @version 1.5.0
* @see io.github.og4dev.annotation.AutoTrim
* @see io.github.og4dev.annotation.XssCheck
* @see io.github.og4dev.config.ApiResponseAutoConfiguration
* @since 1.3.0
*/
-package io.github.og4dev.annotation;
-
-
+package io.github.og4dev.annotation;
\ No newline at end of file
diff --git a/src/main/java/io/github/og4dev/config/ApiResponseAutoConfiguration.java b/src/main/java/io/github/og4dev/config/ApiResponseAutoConfiguration.java
index f5df5a7..3d10b4a 100644
--- a/src/main/java/io/github/og4dev/config/ApiResponseAutoConfiguration.java
+++ b/src/main/java/io/github/og4dev/config/ApiResponseAutoConfiguration.java
@@ -4,7 +4,9 @@
import io.github.og4dev.annotation.AutoResponse;
import io.github.og4dev.annotation.AutoTrim;
import io.github.og4dev.annotation.XssCheck;
+import io.github.og4dev.exception.ApiExceptionRegistry;
import io.github.og4dev.exception.GlobalExceptionHandler;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
@@ -64,7 +66,7 @@
*
*
* @author Pasindu OG
- * @version 1.4.0
+ * @version 1.5.0
* @see GlobalExceptionHandler
* @see GlobalResponseWrapper
* @see org.springframework.boot.autoconfigure.AutoConfiguration
@@ -88,11 +90,13 @@ public ApiResponseAutoConfiguration() {
* {@link org.springframework.web.bind.annotation.RestControllerAdvice} mechanism,
* automatically converting various exceptions to RFC 9457 ProblemDetail responses.
*
- * * @return A new instance of {@link GlobalExceptionHandler} registered as a Spring bean.
+ *
+ * @param registry optional registry used to map external exception types
+ * @return A new instance of {@link GlobalExceptionHandler} registered as a Spring bean.
*/
@Bean
- public GlobalExceptionHandler apiResponseAdvisor() {
- return new GlobalExceptionHandler();
+ public GlobalExceptionHandler apiResponseAdvisor(@Autowired(required = false) ApiExceptionRegistry registry) {
+ return new GlobalExceptionHandler(registry);
}
/**
@@ -123,13 +127,13 @@ public GlobalExceptionHandler apiResponseAdvisor() {
* @RequestMapping("/api/users")
* @AutoResponse // Enables automatic wrapping for all methods in this controller
* public class UserController {
- * * @GetMapping("/{id}")
+ * @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
+ * @PostMapping
* @ResponseStatus(HttpStatus.CREATED)
* public UserDto createUser(@RequestBody UserDto dto) {
* // The 201 Created status will be preserved in the final ApiResponse
@@ -163,103 +167,11 @@ public GlobalResponseWrapper globalResponseWrapper(ObjectMapper objectMapper) {
* trimmed or XSS-validated unless explicitly annotated.
*
*
- *
Overview of Features
- *
- * This configuration provides four critical layers of protection and data processing:
- *
- *
- * - Strict Property Validation - Prevents mass assignment attacks (automatic)
- * - Case-Insensitive Enum Handling - Improves API usability (automatic)
- * - Opt-in XSS Prevention - Blocks HTML/XML injection attacks (requires {@code @XssCheck} on field or class)
- * - Opt-in String Trimming - Removes whitespace (requires {@code @AutoTrim} on field or class)
- *
- *
- *
Feature 1: Strict Property Validation
- *
- * Enables {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} to reject JSON
- * payloads containing unexpected fields. This prevents three critical security issues:
- *
- *
- * - Mass Assignment Vulnerabilities: Attackers cannot inject fields like {@code isAdmin: true}
- * - Data Injection Attacks: Prevents modification of unintended database fields
- * - Client Errors: Detects typos early, preventing silent data loss
- *
- *
- *
Feature 2: Case-Insensitive Enum Handling
- *
- * Enables {@link MapperFeature#ACCEPT_CASE_INSENSITIVE_ENUMS} for flexible enum
- * deserialization. Clients can send enum values in any case format, improving API
- * usability without compromising type safety or security.
- *
- *
- *
Feature 3: Opt-in XSS Prevention with @XssCheck
- *
- * Registers a custom {@link StdScalarDeserializer} ({@code AdvancedStringDeserializer})
- * that performs automatic HTML/XML tag detection and rejection at the deserialization layer
- * for fields or classes annotated with {@link XssCheck @XssCheck}. This provides fail-fast security
- * that prevents malicious content from ever entering your system.
- *
- *
- *
Feature 4: Opt-in String Trimming with @AutoTrim
- *
- * Automatically removes leading and trailing whitespace from string fields or entire classes annotated with
- * {@link AutoTrim @AutoTrim}, improving data quality and preventing common user input errors.
- *
- *
- *
Combining Features (Class and Field Level)
- *
- * You can combine these annotations at both the class and field levels. Class-level annotations
- * apply to all string fields within the class automatically:
- *
- *
{@code
- * @AutoTrim // Automatically trims ALL strings in this class
- * public class SecureDTO {
- * @XssCheck
- * private String cleanInput; // Both trimmed (from class scope) and XSS-validated
- * * private String email; // Only trimmed (from class scope)
- * }
- * }
- *
- *
Implementation Details
- *
- * This method registers an inner class {@code AdvancedStringDeserializer} that extends
- * {@link StdScalarDeserializer}{@code }. The deserializer operates in different modes
- * based on annotations:
- *
- *
- * - Default Mode: No processing (preserves original value)
- * - Trim Mode: {@code shouldTrim = true} when {@code @AutoTrim} is present on the field or class
- * - XSS Mode: {@code shouldXssCheck = true} when {@code @XssCheck} is present on the field or class
- * - Combined Mode: Both trimming and validation when both annotations are present
- *
- *
- * The {@code createContextual()} method inspects each field's annotations, as well as its
- * declaring class's annotations, during deserialization context creation and returns an
- * appropriately configured deserializer instance.
- *
- *
- *
Null Value Handling
- *
- * Null values are preserved and never converted to empty strings.
- *
- *
- *
Performance Considerations
- *
- * The regex validation and trimming operations are highly optimized and add negligible overhead
- * (typically {@code <1ms} per request). The contextual deserializer is created once per field
- * during mapper initialization, not on every request, ensuring optimal runtime performance.
- *
- *
- * @return A {@link JsonMapperBuilderCustomizer} that configures strict JSON processing
- * with opt-in string validation via {@code @XssCheck}, opt-in trimming via {@code @AutoTrim},
- * unknown property rejection, and case-insensitive enum handling.
+ * @return A {@link JsonMapperBuilderCustomizer} that configures strict JSON processing.
* @see DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES
* @see MapperFeature#ACCEPT_CASE_INSENSITIVE_ENUMS
* @see io.github.og4dev.annotation.AutoTrim
* @see io.github.og4dev.annotation.XssCheck
- * @see JsonMapperBuilderCustomizer
- * @see StdScalarDeserializer
- * @see ValueDeserializer#createContextual(DeserializationContext, BeanProperty)
* @since 1.1.0
*/
@Bean
@@ -267,53 +179,68 @@ public JsonMapperBuilderCustomizer strictJsonCustomizer() {
return builder -> {
builder.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
builder.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
+
SimpleModule stringTrimModule = new SimpleModule();
+ stringTrimModule.addDeserializer(String.class, new AdvancedStringDeserializer());
+ builder.addModules(stringTrimModule);
+ };
+ }
+
+ /**
+ * A specialized deserializer that applies opt-in string trimming and XSS validation.
+ * Extracted as a private static class to reduce cognitive complexity.
+ */
+ private static class AdvancedStringDeserializer extends StdScalarDeserializer
{
+ private final boolean shouldTrim;
+ private final boolean shouldXssCheck;
- class AdvancedStringDeserializer extends StdScalarDeserializer {
- private final boolean shouldTrim;
- private final boolean shouldXssCheck;
+ public AdvancedStringDeserializer() {
+ super(String.class);
+ this.shouldTrim = false;
+ this.shouldXssCheck = false;
+ }
- public AdvancedStringDeserializer() {
- super(String.class);
- this.shouldTrim = false;
- this.shouldXssCheck = false;
- }
+ public AdvancedStringDeserializer(boolean shouldTrim, boolean shouldXssCheck) {
+ super(String.class);
+ this.shouldTrim = shouldTrim;
+ this.shouldXssCheck = shouldXssCheck;
+ }
- public AdvancedStringDeserializer(boolean shouldTrim, boolean shouldXssCheck) {
- super(String.class);
- this.shouldTrim = shouldTrim;
- this.shouldXssCheck = shouldXssCheck;
- }
+ @Override
+ public String deserialize(JsonParser p, DeserializationContext text) throws JacksonException {
+ String value = p.getValueAsString();
+ if (value == null) {
+ return null;
+ }
- @Override
- public String deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
- String value = p.getValueAsString();
- if (value == null) return null;
- String processedValue = shouldTrim ? value.trim() : value;
- 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;
- }
+ String processedValue = shouldTrim ? value.trim() : value;
+
+ 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) throws JacksonException {
- if (property != null) {
- boolean trim = property.getAnnotation(AutoTrim.class) != null;
- boolean xss = property.getAnnotation(XssCheck.class) != null;
+ @Override
+ public ValueDeserializer> createContextual(DeserializationContext ct, BeanProperty property) throws JacksonException {
+ if (property == null) {
+ return this;
+ }
- if ((!trim || !xss) && property.getMember() != null) {
- Class> declaringClass = property.getMember().getDeclaringClass();
- if (declaringClass != null) {
- if (!trim) trim = declaringClass.getAnnotation(AutoTrim.class) != null;
- if (!xss) xss = declaringClass.getAnnotation(XssCheck.class) != null;
- }
- }
- return new AdvancedStringDeserializer(trim, xss);
- }
- return this;
+ boolean trim = property.getAnnotation(AutoTrim.class) != null;
+ boolean xss = property.getAnnotation(XssCheck.class) != null;
+ if (trim && xss) {
+ return new AdvancedStringDeserializer(true, true);
+ }
+ if (property.getMember() != null) {
+ Class> declaringClass = property.getMember().getDeclaringClass();
+ if (declaringClass != null) {
+ trim = trim || declaringClass.getAnnotation(AutoTrim.class) != null;
+ xss = xss || declaringClass.getAnnotation(XssCheck.class) != null;
}
}
- stringTrimModule.addDeserializer(String.class, new AdvancedStringDeserializer());
- builder.addModules(stringTrimModule);
- };
+ return new AdvancedStringDeserializer(trim, xss);
+ }
}
}
\ No newline at end of file
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 3aedd22..950e854 100644
--- a/src/main/java/io/github/og4dev/config/package-info.java
+++ b/src/main/java/io/github/og4dev/config/package-info.java
@@ -12,7 +12,7 @@
*
*
* @author Pasindu OG
- * @version 1.4.0
+ * @version 1.5.0
* @since 1.0.0
*/
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 0c81a03..0fdcba6 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.4.0
+ * @version 1.5.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 6c20b78..e56adf5 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.4.0
+ * @version 1.5.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 77cf7f4..10e38f1 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.4.0
+ * @version 1.5.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/ApiExceptionRegistry.java b/src/main/java/io/github/og4dev/exception/ApiExceptionRegistry.java
new file mode 100644
index 0000000..2ff4a28
--- /dev/null
+++ b/src/main/java/io/github/og4dev/exception/ApiExceptionRegistry.java
@@ -0,0 +1,105 @@
+package io.github.og4dev.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.util.Assert;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A thread-safe registry for dynamically mapping third-party or framework-specific exceptions
+ * to standard HTTP statuses and user-friendly messages.
+ *
+ * This registry allows developers to handle external exceptions (for example, SQL, Mongo, or
+ * authentication exceptions) centrally without writing dedicated {@code @ExceptionHandler}
+ * methods for each type.
+ *
+ *
+ * @author Pasindu OG
+ * @version 1.5.0
+ * @since 1.5.0
+ */
+public class ApiExceptionRegistry {
+
+ private final Map, ExceptionRule> registry = Collections.synchronizedMap(new LinkedHashMap<>());
+
+ /**
+ * Registers a custom or third-party exception type with a specific HTTP status and message.
+ *
+ * Registration order matters when both parent and child exception types are registered.
+ * The first assignable entry wins during lookup.
+ *
+ *
+ * @param exceptionClass the exception class to map
+ * @param status the HTTP status to return
+ * @param defaultMessage the fallback client-facing message
+ * @param the exception type
+ * @return this registry for fluent chaining
+ * @throws IllegalArgumentException if any argument is null or message is blank
+ */
+ @SuppressWarnings("unused")
+ public ApiExceptionRegistry register(Class exceptionClass, HttpStatus status, String defaultMessage) {
+ Assert.notNull(exceptionClass, "Exception class must not be null");
+ Assert.notNull(status, "HttpStatus must not be null");
+ Assert.hasText(defaultMessage, "Default message must not be empty or null");
+
+ registry.put(exceptionClass, new ExceptionRule(status, defaultMessage));
+ return this;
+ }
+
+ /**
+ * Returns the first matching rule for the given thrown exception type.
+ *
+ * @param exceptionClass the thrown exception class
+ * @return matching rule or {@code null} when no mapping exists
+ */
+ public ExceptionRule getRule(Class extends Exception> exceptionClass) {
+ if (exceptionClass == null) {
+ return null;
+ }
+
+ synchronized (registry) {
+ for (Map.Entry, ExceptionRule> entry : registry.entrySet()) {
+ if (entry.getKey().isAssignableFrom(exceptionClass)) {
+ return entry.getValue();
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Value object containing status and message for a registered exception mapping.
+ */
+ @SuppressWarnings("ClassCanBeRecord")
+ public static class ExceptionRule {
+ private final HttpStatus status;
+ private final String message;
+
+ /**
+ * Creates a new exception mapping rule.
+ *
+ * @param status mapped HTTP status
+ * @param message mapped client-facing message
+ */
+ public ExceptionRule(HttpStatus status, String message) {
+ this.status = status;
+ this.message = message;
+ }
+
+ /**
+ * @return mapped HTTP status
+ */
+ public HttpStatus getStatus() {
+ return status;
+ }
+
+ /**
+ * @return mapped client-facing message
+ */
+ public String getMessage() {
+ return message;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/github/og4dev/exception/GlobalExceptionHandler.java b/src/main/java/io/github/og4dev/exception/GlobalExceptionHandler.java
index e2a3499..cc6b143 100644
--- a/src/main/java/io/github/og4dev/exception/GlobalExceptionHandler.java
+++ b/src/main/java/io/github/og4dev/exception/GlobalExceptionHandler.java
@@ -3,6 +3,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
@@ -34,28 +35,28 @@
* Supported Exception Types (10 handlers):
*
*
- * - General Exceptions - Catches all unhandled exceptions (HTTP 500)
- * - Validation Errors - {@code @Valid} annotation failures (HTTP 400)
- * - Type Mismatches - Method argument type conversion errors (HTTP 400)
- * - Malformed JSON - Invalid request body format (HTTP 400)
- * - Missing Parameters - Required {@code @RequestParam} missing (HTTP 400)
- * - 404 Not Found - Missing endpoints or resources (HTTP 404)
- * - Method Not Allowed - Unsupported HTTP methods (HTTP 405)
- * - Unsupported Media Type - Invalid Content-Type headers (HTTP 415)
- * - Null Pointer Exceptions - NullPointerException handling (HTTP 500)
- * - Custom API Exceptions - Domain-specific business logic errors (custom status)
+ * - General Exceptions - Catches all unhandled exceptions (HTTP 500)
+ * - Validation Errors - {@code @Valid} annotation failures (HTTP 400)
+ * - Type Mismatches - Method argument type conversion errors (HTTP 400)
+ * - Malformed JSON - Invalid request body format (HTTP 400)
+ * - Missing Parameters - Required {@code @RequestParam} missing (HTTP 400)
+ * - 404 Not Found - Missing endpoints or resources (HTTP 404)
+ * - Method Not Allowed - Unsupported HTTP methods (HTTP 405)
+ * - Unsupported Media Type - Invalid Content-Type headers (HTTP 415)
+ * - Null Pointer Exceptions - NullPointerException handling (HTTP 500)
+ * - Custom API Exceptions - Domain-specific business logic errors (custom status)
*
*
* Error Response Format (RFC 9457 ProblemDetail):
*
*
- * - type - URI reference identifying the problem type (defaults to "about:blank")
- * - title - Short, human-readable summary of the problem
- * - status - HTTP status code
- * - detail - Human-readable explanation specific to this occurrence
- * - traceId - Unique UUID for request correlation and debugging
- * - timestamp - RFC 3339 UTC timestamp
- * - errors - Validation field errors (for validation failures only)
+ * - type - URI reference identifying the problem type (defaults to "about:blank")
+ * - title - Short, human-readable summary of the problem
+ * - status - HTTP status code
+ * - detail - Human-readable explanation specific to this occurrence
+ * - traceId - Unique UUID for request correlation and debugging
+ * - timestamp - RFC 3339 UTC timestamp
+ * - errors - Validation field errors (for validation failures only)
*
*
* Trace ID Management: All exception handlers ensure consistent trace IDs between logs
@@ -70,12 +71,12 @@
* Logging: All exceptions are automatically logged with appropriate severity levels:
*
*
- * - ERROR - General exceptions, null pointer exceptions
- * - WARN - Validation errors, type mismatches, business logic exceptions, 400/404/405/415 errors
+ * - ERROR - General exceptions, null pointer exceptions
+ * - WARN - Validation errors, type mismatches, business logic exceptions, 400/404/405/415 errors
*
*
* @author Pasindu OG
- * @version 1.4.0
+ * @version 1.5.0
* @since 1.0.0
* @see org.springframework.web.bind.annotation.RestControllerAdvice
* @see org.springframework.http.ProblemDetail
@@ -92,11 +93,15 @@
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
+ private final ApiExceptionRegistry registry;
+
/**
- * Default constructor for Spring bean instantiation.
+ * Constructor for Spring bean instantiation, accepting an optional ApiExceptionRegistry.
+ *
+ * @param registry the optional API exception registry for custom exception mapping
*/
- public GlobalExceptionHandler() {
- // Default constructor for Spring bean instantiation
+ public GlobalExceptionHandler(@Autowired(required = false) ApiExceptionRegistry registry) {
+ this.registry = registry;
}
/**
@@ -114,14 +119,16 @@ private String getOrGenerateTraceId() {
}
/**
- * Handles all unhandled exceptions.
+ * Handles all unhandled exceptions, logs them with stack trace details,
+ * and dynamically maps them via ApiExceptionRegistry if configured.
*
* @param ex the exception
- * @return ProblemDetail response with 500 status
+ * @return ProblemDetail response with appropriate status
*/
@ExceptionHandler(Exception.class)
public ProblemDetail handleAllExceptions(Exception ex) {
String traceId = getOrGenerateTraceId();
+
StackTraceElement rootCause = ex.getStackTrace().length > 0 ? ex.getStackTrace()[0] : null;
String className = (rootCause != null) ? rootCause.getClassName() : "Unknown Class";
int lineNumber = (rootCause != null) ? rootCause.getLineNumber() : -1;
@@ -129,9 +136,21 @@ public ProblemDetail handleAllExceptions(Exception ex) {
log.error("[TraceID: {}] Error in {}:{} - Message: {}",
traceId, className, lineNumber, ex.getMessage());
+ // Check if the exception is registered in the ApiExceptionRegistry
+ if (registry != null) {
+ ApiExceptionRegistry.ExceptionRule rule = registry.getRule(ex.getClass());
+ if (rule != null) {
+ ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(rule.getStatus(), rule.getMessage());
+ problemDetail.setProperty("traceId", traceId);
+ problemDetail.setProperty("timestamp", Instant.now());
+ return problemDetail;
+ }
+ }
+
+ // Default Fallback
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
HttpStatus.INTERNAL_SERVER_ERROR,
- "Internal Server Error. Please contact technical support");
+ "Internal Server Error. Please contact technical support.");
problemDetail.setProperty("traceId", traceId);
problemDetail.setProperty("timestamp", Instant.now());
return problemDetail;
@@ -170,8 +189,14 @@ public ProblemDetail handleValidationExceptions(MethodArgumentNotValidException
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ProblemDetail handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) {
String traceId = getOrGenerateTraceId();
+
+ // SonarQube Fix: Assigning getRequiredType() to a variable before checking for null
+ Class> requiredType = ex.getRequiredType();
+ String expectedType = (requiredType != null) ? requiredType.getSimpleName() : "Unknown";
+
String errorMessage = String.format("Invalid value '%s' for parameter '%s'. Expected type: %s.",
- ex.getValue(), ex.getName(), ex.getRequiredType() != null ? ex.getRequiredType().getSimpleName() : "Unknown");
+ ex.getValue(), ex.getName(), expectedType);
+
log.warn("[TraceID: {}] Type mismatch error: {}", traceId, errorMessage);
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, errorMessage);
problemDetail.setProperty("traceId", traceId);
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 6e81223..e41ea33 100644
--- a/src/main/java/io/github/og4dev/exception/package-info.java
+++ b/src/main/java/io/github/og4dev/exception/package-info.java
@@ -26,8 +26,7 @@
*
*
* @author Pasindu OG
- * @version 1.4.0
+ * @version 1.5.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 658d146..aa67152 100644
--- a/src/main/java/io/github/og4dev/filter/TraceIdFilter.java
+++ b/src/main/java/io/github/og4dev/filter/TraceIdFilter.java
@@ -30,7 +30,7 @@
* Compatible with microservices architectures
*
*
- * Usage: Register this filter as a Spring bean with highest precedence:
+ * Usage: Register this filter as a Spring bean with the highest precedence:
*
* {@code
* @Configuration
@@ -57,7 +57,7 @@
*
*
* @author Pasindu OG
- * @version 1.4.0
+ * @version 1.5.0
* @since 1.0.0
* @see org.springframework.web.filter.OncePerRequestFilter
* @see org.slf4j.MDC
@@ -96,4 +96,4 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
MDC.clear();
}
}
-}
+}
\ No newline at end of file
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 dcff3f0..4bbbf7c 100644
--- a/src/main/java/io/github/og4dev/filter/package-info.java
+++ b/src/main/java/io/github/og4dev/filter/package-info.java
@@ -21,8 +21,7 @@
*
*
* @author Pasindu OG
- * @version 1.4.0
+ * @version 1.5.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 a471b48..efb2abf 100644
--- a/src/main/java/io/github/og4dev/package-info.java
+++ b/src/main/java/io/github/og4dev/package-info.java
@@ -10,8 +10,7 @@
*
*
* @author Pasindu OG
- * @version 1.4.0
+ * @version 1.5.0
* @since 1.0.0
*/
package io.github.og4dev;
-