If you have docker installed, then simply run the application in docker
profile by passing spring.profiles.active=docker
as run time argument from your IDE.
Depending on your current working directory in IDE, you may need to change spring.docker.compose.file=problem-handler-webflux-demo/compose.yml
to spring.docker.compose.file=compose.yml
in application-docker.properties
Make sure the host ports mapped in Docker compose file
are available or change the ports and
do the respective changes in database configurations application-docker.properties
Update following properties with your PostgresDB configurations
spring.r2dbc.url=${POSTGRES_URL:r2dbc:postgresql://localhost:5432/problem_webflux_db}
spring.r2dbc.username=${POSTGRES_USER:postgres}
spring.r2dbc.password=${POSTGRES_USER:admin}
spring.flyway.url=${POSTGRES_URL:jdbc:postgresql://localhost:5432/problem_webflux_db}
spring.flyway.user=${POSTGRES_USER:postgres}
spring.flyway.password=${POSTGRES_PASSWORD:admin}
Update following properties with your MongoDB configurations
spring.data.mongodb.uri=${MONGODB_URL:mongodb://localhost:27017/problem_web_db}
Run the main class ProblemWebFluxDemoApplication
and access Swagger Swagger
at http://localhost:8080/swagger-ui.html
Select Application
from dropdown Select a definition
- State management APIs are using MongoDB, to test database constraint violations.
- Employee management APIs are using PostgresDB, to test database constraint violations.
- Problem Demo APIs throws exceptions explicitly.
Have a look at
DemoProblemController
- State and Employee management APIs are secured, so need to pass a JWT token in
Authorization
header. See the lock symbol against the API in Swagger
Click on Authorize button to pass the JWT Token. Use any valid JWT Token.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- Call the APIs providing invalid inputs to make it throw exception and have a look at response.
- Set
problem.debug-enabled=true
inapplication.properties
to get the message resolvers and set the messages inerrors.properties
to customize the error response attributes in response. - Test with setting
problem.stacktrace-enabled=true
andproblem.cause-chains-enabled=true
inapplication.properties
to get Stacktrace and Cause in response. - Update
help.html
with any custom error description and follow thetype
url in error response to see the error description on help page. - Follow http://localhost:8080/problems/help.html to see the description of errors.
Following are example error responses in different scenarios.
The error response attributes code
, title
and detail
can be customized for each error by specifying
the same in errors.properties
file for different error keys which you can get by setting problem.debug-enabled=true
in application.properties
file
Most common type of errors an application must handle
Code
@Valid
@Getter
@Setter
public static final class UserRequest {
@Size(min = 3, max = 10)
private String name;
@Size(min = 2, max = 5)
private String designation;
@NotNull
@Valid
private Address address;
}
@Getter
@Setter
@Valid
public static final class Address {
@NotEmpty
private String city;
@NotEmpty
private String state;
}
Request
curl -X 'POST' \
'http://localhost:8080/problems/handler-constraint-violation' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"name": "a",
"designation": "aaaaaaaaaaaaaaaaa",
"address": {
"city": "string"
}
}'
Response
{
"type": "http://localhost:8080/problems/help.html#constraint-violations",
"title": "Bad Request",
"status": 400,
"detail": "Constraint violations has happened, please correct the request and try again",
"instance": "/problems/handler-constraint-violation",
"method": "POST",
"timestamp": "2023-10-29T16:41:59.876471+05:30",
"code": "constraint-violations",
"violations": [
{
"code": "400",
"detail": "User name length should be between 3 and 10",
"propertyPath": "name"
},
{
"code": "400",
"detail": "Address state name is required",
"propertyPath": "address.state"
},
{
"code": "400",
"detail": "User designation length should be between 2 and 5",
"propertyPath": "designation"
}
]
}
Make following request two time, 2nd time the exception will be thrown.
Request
curl -X 'POST' \
'http://localhost:8080/api/employees' \
-H 'accept: */*' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' \
-H 'Content-Type: application/json' \
-d '{
"name": "John Rambo",
"dob": "1983-06-06"
}'
Response
{
"type": "http://localhost:8080/problems/help.html#500",
"title": "Internal Server Error",
"status": 500,
"detail": "Employee name must be unique, a record with given name already exists",
"instance": "/api/employees",
"method": "POST",
"timestamp": "2023-10-29T16:44:10.917194+05:30",
"code": "500"
}
Make following request two time, 2nd time the exception will be thrown.
Request
curl -X 'POST' \
'http://localhost:8080/api/states' \
-H 'accept: */*' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' \
-H 'Content-Type: application/json' \
-d '{
"code": "HR",
"name": "Haryana",
"gstCode": "6"
}'
Response
{
"type": "http://localhost:8080/problems/help.html#500",
"title": "Internal Server Error",
"status": 500,
"detail": "State name must be unique",
"instance": "/api/states",
"method": "POST",
"timestamp": "2023-10-29T16:44:44.806613+05:30",
"code": "500"
}
Request
curl -X 'GET' \
'http://localhost:8080/problems/handler-invalid-query-strings?page=-1&size=1' \
-H 'accept: */*'
Response
{
"type": "http://localhost:8080/problems/help.html#constraint-violations",
"title": "Bad Request",
"status": 400,
"detail": "Constraint violations has happened, please correct the request and try again",
"instance": "/problems/handler-invalid-query-strings",
"method": "GET",
"timestamp": "2023-10-29T14:51:37.889537+05:30",
"code": "constraint-violations",
"violations": [
{
"code": "400",
"detail": "must be greater than or equal to 0",
"propertyPath": "page"
}
]
}
Request
curl -X 'GET' \
'http://localhost:8080/problems/handler-datetime-conversion?dateTime=2023-13-18T10%3A12%3A12Z' \
-H 'accept: */*'
Response
{
"type": "http://localhost:8080/problems/help.html#400",
"title": "Bad Request",
"status": 400,
"detail": "Invalid date time value or format. Expected a valid date time in ISO format",
"instance": "/problems/handler-datetime-conversion",
"method": "GET",
"timestamp": "2023-10-29T16:05:09.953099+05:30",
"code": "400",
"propertyPath": "dateTime"
}
Request
curl -X 'POST' \
'http://localhost:8080/problems/uploadfile' \
-H 'accept: */*' \
-H 'Content-Type: multipart/form-data' \
-F 'file=@Large_File.pdf;type=application/pdf'
Response
{
"type": "http://localhost:8080/problems/help.html#400",
"title": "Bad Request",
"status": 400,
"detail": "Part exceeded the disk usage limit of 1024 bytes",
"instance": "/problems/uploadfile",
"method": "POST",
"timestamp": "2023-10-29T20:50:07.366581+05:30",
"code": "400"
}
Request
curl -X 'POST' \
'http://localhost:8080/problems/handler-json-body' \
-H 'accept: */*' \
-H 'Content-Type: application/xml' \
-d '{
"test": "string"
}'
Response
{
"type": "http://localhost:8080/problems/help.html#415",
"title": "Unsupported Media Type",
"status": 415,
"detail": "Media Type: application/xml Not Acceptable, Supported Media Types are: application/json",
"instance": "/problems/handler-json-body",
"method": "POST",
"timestamp": "2023-10-29T14:45:47.467268+05:30",
"code": "415"
}
Request
curl -X 'POST' \
'http://localhost:8080/problems/handler-datetime-conversion?dateTime=2023-04-18T10%3A12%3A12Z' \
-H 'accept: */*'
Response
{
"type": "http://localhost:8080/problems/help.html#405",
"title": "Method Not Allowed",
"status": 405,
"detail": "Requested Method: POST not allowed, allowed methods are: GET",
"instance": "/problems/handler-datetime-conversion",
"method": "POST",
"timestamp": "2023-10-29T16:15:08.916369+05:30",
"code": "405"
}
Code
throw new IllegalArgumentException("Expected argument invalid", new IllegalStateException("Dummy cause"));
Request
curl -X 'GET' \
'http://localhost:8080/problems/handler-throwable' \
-H 'accept: */*'
Response
{
"type": "http://localhost:8080/problems/help.html#500",
"title": "Internal Server Error",
"status": 500,
"detail": "Expected argument invalid",
"instance": "/problems/handler-throwable",
"method": "GET",
"timestamp": "2023-10-29T14:49:40.998497+05:30",
"code": "500"
}
Code
Problem problem = Problems.newInstance("3456", "Bad Request", "Invalid request received, Please retry with correct input")
.parameter("additional-attribute", "Some additional attribute").build();
throw Problems.throwAble(HttpStatus.BAD_REQUEST, problem);
Request
curl -X 'GET' \
'http://localhost:8080/problems/throw-problem-with-additional-attribute' \
-H 'accept: */*'
Response
{
"type": "http://localhost:8080/problems/help.html#3456",
"title": "Bad Request",
"status": 400,
"detail": "Invalid request received, Please retry with correct input",
"instance": "/problems/throw-problem-with-additional-attribute",
"method": "GET",
"timestamp": "2023-10-29T16:24:37.976724+05:30",
"code": "3456",
"additional-attribute": "Some additional attribute"
}
Code
ApplicationException problemOne = Problems.newInstance("sample.problem.one").throwAbleChecked();
ApplicationProblem problemTwo = Problems.newInstance(AppErrors.REMOTE_HOST_NOT_AVAILABLE).detailArgs("http://some.remote.host.com").throwAble();
MultiProblem problems = Problems.ofExceptions(HttpStatus.MULTI_STATUS, problemOne, problemTwo);
Problem problemThree = Problems.newInstance("3456", "Bad Request", "Invalid request received, Please retry with correct input")
.parameter("additional-attribute", "Some additional attribute").build();
problems.add(problemThree);
Exception exception = new IllegalStateException("Just for testing exception");
problems.add(exception);
Problem problem = Problems.newInstance("111", "Dummy", "Hardcode attributes broblem").build();
problems.add(problem);
throw problems;
Request
curl -X 'GET' \
'http://localhost:8080/problems/throw-multiple-problems' \
-H 'accept: */*'
Response
{
"type": "http://localhost:8080/problems/help.html#207",
"title": "Multi-Status",
"status": 207,
"detail": "Multi-Status",
"instance": "/problems/throw-multiple-problems",
"method": "GET",
"timestamp": "2023-10-29T16:22:53.363785+05:30",
"code": "207",
"errors": [
{
"code": "500",
"title": "Internal Server Error",
"detail": "Sample error message defined in 'errors.properties'"
},
{
"code": "503",
"title": "Service Unavailable",
"detail": "Looks like something wrong with remote host: http://some.remote.host.com"
},
{
"code": "3456",
"title": "Bad Request",
"detail": "Invalid request received, Please retry with correct input",
"additional-attribute": "Some additional attribute"
},
{
"code": "500",
"title": "Internal Server Error",
"detail": "Just for testing exception"
},
{
"code": "111",
"title": "Dummy",
"detail": "Hardcode attributes broblem"
}
]
}
Request
curl -X 'POST' \
'http://localhost:8080/api/pets' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"id": 0,
"name": "string",
"category": "string",
"tags": [
"string"
],
"status": "AVAILABLE"
}'
Response
{
"type": "http://localhost:8080/problems/help.html#constraint-violations",
"title": "Bad Request",
"status": 400,
"detail": "Constraint violations has happened, please correct the request and try again",
"instance": "/api/pets",
"method": "POST",
"timestamp": "2023-10-29T16:06:18.335463+05:30",
"code": "constraint-violations",
"violations": [
{
"code": "400",
"detail": "[Path '/id'] Numeric instance is lower than the required minimum (minimum: 1, found: 0)"
}
]
}
Request
curl -X 'GET' \
'http://localhost:8080/api/employees/1' \
-H 'accept: */*'
Response
{
"type": "http://localhost:8080/problems/help.html#401",
"title": "Unauthorized",
"status": 401,
"detail": "Either Authorization header bearer token is missing or invalid",
"instance": "/api/employees/1",
"method": "GET",
"timestamp": "2023-10-29T16:08:40.466566+05:30",
"code": "401"
}