Skip to content

Commit 90604dd

Browse files
committed
Merge tag '5.1.0'
2 parents f649d58 + 67c7fdd commit 90604dd

28 files changed

+969
-31
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Publish versioned docs
2+
on:
3+
workflow_dispatch:
4+
5+
6+
jobs:
7+
build:
8+
name: "Build with ${{ matrix.java }}"
9+
strategy:
10+
matrix:
11+
java: [ 17 ]
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v2
15+
16+
- name: Setup java ${{ matrix.java }}
17+
uses: actions/setup-java@v4
18+
with:
19+
java-version: ${{ matrix.java }}
20+
distribution: 'temurin'
21+
cache: maven
22+
23+
- name: Build with Maven
24+
run: ./mvnw -B -ntp clean verify
25+
26+
- name: Set Release version env variable
27+
run: |
28+
echo "RELEASE_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV
29+
30+
- name: GitHub Pages action (versioned dir)
31+
uses: peaceiris/actions-gh-pages@v3
32+
with:
33+
github_token: ${{ secrets.GITHUB_TOKEN }}
34+
publish_dir: ./target/generated-docs
35+
destination_dir: ${{ env.RELEASE_VERSION }}
36+
exclude_assets: 'img/banner-logo.svg,img/doc-background.svg,img/doc-background-dark.svg'

README.adoc

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,21 @@ NOTE: Documentation is very important to us, so if you find something missing fr
3333
|===
3434
|error-handling-spring-boot-starter |Spring Boot|Minimum Java version|Docs
3535

36+
|https://github.com/wimdeblauwe/error-handling-spring-boot-starter/releases/tag/5.1.0[5.1.0]
37+
|4.0.x
38+
|17
39+
|https://wimdeblauwe.github.io/error-handling-spring-boot-starter/5.1.0/[Documentation 5.1.0]
40+
3641
|https://github.com/wimdeblauwe/error-handling-spring-boot-starter/releases/tag/5.0.1[5.0.1]
37-
|4.0.0
42+
|4.0.x
3843
|17
3944
|https://wimdeblauwe.github.io/error-handling-spring-boot-starter/5.0.1/[Documentation 5.0.1]
4045

46+
|https://github.com/wimdeblauwe/error-handling-spring-boot-starter/releases/tag/4.7.0[4.7.0]
47+
|3.5.x
48+
|17
49+
|https://wimdeblauwe.github.io/error-handling-spring-boot-starter/4.7.0/[Documentation 4.7.0]
50+
4151
|https://github.com/wimdeblauwe/error-handling-spring-boot-starter/releases/tag/4.6.0[4.6.0]
4252
|3.3.x, 3.4.x, 3.5.x
4353
|17

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
<parent>
55
<groupId>org.springframework.boot</groupId>
66
<artifactId>spring-boot-starter-parent</artifactId>
7-
<version>4.0.0</version>
7+
<version>4.0.2</version>
88
<relativePath /> <!-- lookup parent from repository -->
99
</parent>
1010
<groupId>io.github.wimdeblauwe</groupId>
1111
<artifactId>error-handling-spring-boot-starter</artifactId>
12-
<version>5.0.1</version>
12+
<version>5.1.0</version>
1313
<name>Error Handling Spring Boot Starter</name>
1414
<description>Spring Boot starter that configures error handling</description>
1515

src/docs/asciidoc/index.adoc

Lines changed: 202 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,28 @@ With this configuration, 400 Bad Request will be printed on DEBUG level.
12951295
401 Unauthorized will be printed on INFO.
12961296
Finally, all status code in the 5xx range will be printed on ERROR.
12971297

1298+
==== Filter logging
1299+
1300+
If you like to filter certain exceptions from being logged (while you still want the exception handling to happen), then you can declare a `@Bean` of type `io.github.wimdeblauwe.errorhandlingspringbootstarter.LoggingServiceFilter`:
1301+
1302+
[source,java]
1303+
----
1304+
import io.github.wimdeblauwe.errorhandlingspringbootstarter.LoggingServiceFilter;
1305+
import org.springframework.stereotype.Component;
1306+
1307+
@Component
1308+
public class MyLoggingServiceFilter implements LoggingServiceFilter {
1309+
@Override
1310+
public boolean shouldLogException(ApiErrorResponse errorResponse,
1311+
Throwable exception) {
1312+
if( exception instanceof SomeException ) {
1313+
return false;
1314+
}
1315+
return true;
1316+
}
1317+
}
1318+
----
1319+
12981320
=== Spring Security
12991321

13001322
==== AuthenticationEntryPoint
@@ -1459,6 +1481,173 @@ Those are implementations of `jakarta.servlet.Filter`, usually subclasses of `or
14591481

14601482
By setting the property `error.handling.handle-filter-chain-exceptions` to `true`, the library will handle those exceptions and return error responses just like is done for exceptions coming from controller methods.
14611483

1484+
=== Problem Detail format (RFC 9457)
1485+
1486+
The library supports the https://www.rfc-editor.org/rfc/rfc9457[RFC 9457] Problem Detail format as an alternative to the default JSON response format.
1487+
When enabled, error responses will use the standardized Problem Detail structure and the `application/problem+json` content type.
1488+
1489+
==== Enabling Problem Detail format
1490+
1491+
To enable Problem Detail format, set the following property:
1492+
1493+
[source,properties]
1494+
----
1495+
error.handling.use-problem-detail-format=true
1496+
----
1497+
1498+
==== Response format comparison
1499+
1500+
When Problem Detail format is disabled (default), the response looks like this:
1501+
1502+
[source,json]
1503+
----
1504+
{
1505+
"code": "USER_NOT_FOUND",
1506+
"message": "Could not find user with id 123"
1507+
}
1508+
----
1509+
1510+
When Problem Detail format is enabled, the same error produces:
1511+
1512+
[source,json]
1513+
----
1514+
{
1515+
"type": "user-not-found",
1516+
"title": "Not Found",
1517+
"status": 404,
1518+
"detail": "Could not find user with id 123"
1519+
}
1520+
----
1521+
1522+
The Problem Detail format is built like this:
1523+
1524+
[cols="1,1"]
1525+
|===
1526+
|Problem Detail field | Description
1527+
1528+
|`type`
1529+
|Value of the `code` field (converted to kebab-case by default)
1530+
1531+
|`detail`
1532+
|Value of the `message` field
1533+
1534+
|`title`
1535+
|The HTTP status text (This is coming from Spring itself)
1536+
1537+
|`status`
1538+
|The numeric HTTP status code
1539+
|===
1540+
1541+
==== Configuring the type prefix
1542+
1543+
You can add a prefix to the `type` field to create fully qualified URIs.
1544+
This is useful for providing documentation links for your error types:
1545+
1546+
[source,properties]
1547+
----
1548+
error.handling.use-problem-detail-format=true
1549+
error.handling.problem-detail-type-prefix=https://api.example.com/errors/
1550+
----
1551+
1552+
With this configuration, the response becomes:
1553+
1554+
[source,json]
1555+
----
1556+
{
1557+
"type": "https://api.example.com/errors/user-not-found",
1558+
"title": "Not Found",
1559+
"status": 404,
1560+
"detail": "Could not find user with id 123"
1561+
}
1562+
----
1563+
1564+
==== Disabling kebab-case conversion
1565+
1566+
By default, error codes are converted to kebab-case for the `type` field (e.g., `USER_NOT_FOUND` becomes `user-not-found`).
1567+
To preserve the original error code format, disable the conversion:
1568+
1569+
[source,properties]
1570+
----
1571+
error.handling.use-problem-detail-format=true
1572+
error.handling.problem-detail-convert-to-kebab-case=false
1573+
----
1574+
1575+
Result:
1576+
1577+
[source,json]
1578+
----
1579+
{
1580+
"type": "USER_NOT_FOUND",
1581+
"title": "Not Found",
1582+
"status": 404,
1583+
"detail": "Could not find user with id 123"
1584+
}
1585+
----
1586+
1587+
==== Validation errors with Problem Detail
1588+
1589+
Validation errors include `fieldErrors` and `globalErrors` as additional properties in the Problem Detail response:
1590+
1591+
[source,json]
1592+
----
1593+
{
1594+
"type": "validation-failed",
1595+
"title": "Bad Request",
1596+
"status": 400,
1597+
"detail": "Validation failed for object='exampleRequestBody'. Error count: 2",
1598+
"fieldErrors": [
1599+
{
1600+
"code": "INVALID_SIZE",
1601+
"property": "name",
1602+
"message": "size must be between 10 and 2147483647",
1603+
"rejectedValue": "",
1604+
"path": "name"
1605+
},
1606+
{
1607+
"code": "REQUIRED_NOT_BLANK",
1608+
"property": "favoriteMovie",
1609+
"message": "must not be blank",
1610+
"rejectedValue": null,
1611+
"path": "favoriteMovie"
1612+
}
1613+
]
1614+
}
1615+
----
1616+
1617+
==== Custom properties with Problem Detail
1618+
1619+
Custom properties added via `@ResponseErrorProperty` are included in the Problem Detail response:
1620+
1621+
[source,java]
1622+
----
1623+
@ResponseStatus(HttpStatus.CONFLICT)
1624+
@ResponseErrorCode("OPTIMISTIC_LOCKING_ERROR")
1625+
public class OptimisticLockingException extends RuntimeException {
1626+
1627+
@ResponseErrorProperty
1628+
private final String identifier;
1629+
1630+
@ResponseErrorProperty
1631+
private final String persistentClassName;
1632+
1633+
// constructor and getters
1634+
}
1635+
----
1636+
1637+
With the type prefix configured, the response would be:
1638+
1639+
[source,json]
1640+
----
1641+
{
1642+
"type": "https://api.example.com/errors/optimistic-locking-error",
1643+
"title": "Conflict",
1644+
"status": 409,
1645+
"detail": "Object of class [com.example.user.User] with identifier [1]: optimistic locking failed",
1646+
"identifier": "1",
1647+
"persistentClassName": "com.example.user.User"
1648+
}
1649+
----
1650+
14621651
== Custom exception handler
14631652

14641653
If the <<Configuration,extensive customization options>> are not enough, you can write your own `ApiExceptionHandler` implementation.
@@ -1585,7 +1774,19 @@ When this is set to `true`, you can use any superclass from your `Exception` typ
15851774

15861775
|error.handling.handle-filter-chain-exceptions
15871776
|Set this to `true` to have the library intercept any exception thrown from custom filters and also have the same error responses as exceptions thrown from controller methods.
1588-
|`false`.
1777+
|`false`
1778+
1779+
|error.handling.use-problem-detail-format
1780+
|Set this to `true` to use the https://www.rfc-editor.org/rfc/rfc9457[RFC 9457] Problem Detail format for error responses instead of the default format.
1781+
|`false`
1782+
1783+
|error.handling.problem-detail-type-prefix
1784+
|The prefix to use for the `type` field in Problem Detail responses. This is typically a URL that serves as a namespace for the error types.
1785+
|Empty string
1786+
1787+
|error.handling.problem-detail-convert-to-kebab-case
1788+
|When using Problem Detail format, this controls whether the error code is converted to kebab-case for the `type` field. For example, `USER_NOT_FOUND` would become `user-not-found`.
1789+
|`true`
15891790
|===
15901791

15911792
== Adding Error Responses to OpenAPI Documentation

src/main/java/io/github/wimdeblauwe/errorhandlingspringbootstarter/AbstractErrorHandlingConfiguration.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,15 @@ public ErrorHandlingFacade errorHandlingFacade(List<ApiExceptionHandler> handler
3333

3434
@Bean
3535
@ConditionalOnMissingBean
36-
public LoggingService loggingService(ErrorHandlingProperties properties) {
37-
return new LoggingService(properties);
36+
public LoggingService loggingService(ErrorHandlingProperties properties,
37+
LoggingServiceFilter loggingServiceFilter) {
38+
return new LoggingService(properties, loggingServiceFilter);
39+
}
40+
41+
@Bean
42+
@ConditionalOnMissingBean
43+
public LoggingServiceFilter loggingServiceFilter() {
44+
return new AlwaysLogLoggingServiceFilter();
3845
}
3946

4047
@Bean
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.github.wimdeblauwe.errorhandlingspringbootstarter;
2+
3+
public class AlwaysLogLoggingServiceFilter implements LoggingServiceFilter {
4+
@Override
5+
public boolean shouldLogException(ApiErrorResponse errorResponse,
6+
Throwable exception) {
7+
return true;
8+
}
9+
}

src/main/java/io/github/wimdeblauwe/errorhandlingspringbootstarter/ErrorHandlingProperties.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ public class ErrorHandlingProperties {
4141

4242
private boolean handleFilterChainExceptions = false;
4343

44+
private boolean useProblemDetailFormat = false;
45+
46+
private String problemDetailTypePrefix = "";
47+
48+
private boolean problemDetailConvertToKebabCase = true;
49+
4450
public boolean isEnabled() {
4551
return enabled;
4652
}
@@ -153,6 +159,30 @@ public void setHandleFilterChainExceptions(boolean handleFilterChainExceptions)
153159
this.handleFilterChainExceptions = handleFilterChainExceptions;
154160
}
155161

162+
public boolean isUseProblemDetailFormat() {
163+
return useProblemDetailFormat;
164+
}
165+
166+
public void setUseProblemDetailFormat(boolean useProblemDetailFormat) {
167+
this.useProblemDetailFormat = useProblemDetailFormat;
168+
}
169+
170+
public String getProblemDetailTypePrefix() {
171+
return problemDetailTypePrefix;
172+
}
173+
174+
public void setProblemDetailTypePrefix(String problemDetailTypePrefix) {
175+
this.problemDetailTypePrefix = problemDetailTypePrefix;
176+
}
177+
178+
public boolean isProblemDetailConvertToKebabCase() {
179+
return problemDetailConvertToKebabCase;
180+
}
181+
182+
public void setProblemDetailConvertToKebabCase(boolean problemDetailConvertToKebabCase) {
183+
this.problemDetailConvertToKebabCase = problemDetailConvertToKebabCase;
184+
}
185+
156186
public enum ExceptionLogging {
157187
NO_LOGGING,
158188
MESSAGE_ONLY,

src/main/java/io/github/wimdeblauwe/errorhandlingspringbootstarter/LoggingService.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,19 @@
1111
public class LoggingService {
1212
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingService.class);
1313
private final ErrorHandlingProperties properties;
14+
private final LoggingServiceFilter loggingServiceFilter;
1415

15-
public LoggingService(ErrorHandlingProperties properties) {
16+
public LoggingService(ErrorHandlingProperties properties,
17+
LoggingServiceFilter loggingServiceFilter) {
1618
this.properties = properties;
19+
this.loggingServiceFilter = loggingServiceFilter;
1720
}
1821

1922
public void logException(ApiErrorResponse errorResponse, Throwable exception) {
23+
if(!loggingServiceFilter.shouldLogException(errorResponse, exception)) {
24+
return;
25+
}
26+
2027
HttpStatusCode httpStatus = errorResponse.getHttpStatus();
2128
Objects.requireNonNull(httpStatus);
2229
if (properties.getFullStacktraceClasses().contains(exception.getClass())) {

0 commit comments

Comments
 (0)