Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater.
(NOTE: The Backstopper 1.x branch contains a version of
Backstopper for Java 7+, and for the javax
ecosystem. The current Backstopper supports Java 17+ and the jakarta
ecosystem. The Backstopper 1.x releases also contain support for Spring 4 and 5, and Springboot 1 and 2.)
This submodule contains a sample application based on Spring Boot 3 + WebFlux (Netty) that fully integrates Backstopper.
- Build the sample by running the
./buildSample.sh
script. - Launch the sample by running the
./runSample.sh
script. It will bind to port 8080 by default.- You can override the default port by passing in a system property to the run script,
e.g. to bind to port 8181:
./runSample.sh -Dserver.port=8181
- You can override the default port by passing in a system property to the run script,
e.g. to bind to port 8181:
All examples here assume the sample app is running on port 8080, so you would hit each path by going to
http://localhost:8080/[endpoint-path]
. It's recommended that you use a REST client like
Postman for making the requests so you can easily specify HTTP method, payloads,
headers, etc, and fully inspect the response.
Also note that all the following things to try are verified in a component test:
VerifyExpectedErrorsAreReturnedComponentTest
. If you prefer to experiment via code you can run, debug, and otherwise
explore that test.
As you are doing the following you should check the logs that are output by the sample application and notice what is
included in the log messages. In particular notice how you can search for an error_id
that came from an error
response and go directly to the relevant log message in the logs. Also notice how the ApiError.getName()
value shows
up in the logs for each error represented in a returned error contract (there can be more than one per request).
GET /sample
- Returns the JSON serialization for theSampleModel
model object. You can copy this into aPOST
call to experiment with triggering errors.POST /sample
withContentType: application/json
header - Using the JSON model retrieved by theGET
call, you can trigger numerous different types of errors, all of which get caught by the Backstopper system and converted into the appropriate error contract.- Omit the
foo
field. - Set the value of the
range_0_to_42
field to something outside of the allowed 0-42 range. - Set the value of the
rgb_color
field to something besidesRED
,GREEN
, orBLUE
, or omit it entirely. Note that the validation and deserialization of this enum field is done in a case insensitive manner - i.e. you can passred
,Green
, orbLuE
if you want and it will not throw an error. - Set two or more invalid values for
foo
,range_0_to_42
, andrgb_color
to invalid values all at once - notice you get back all relevant errors at once in the same error contract. - Set
throw_manual_error
to true to trigger a manual exception to be thrown inside the normalPOST /sample
endpoint.- Note the extra response headers that are included when you do this, and how they relate to the
.withExtraResponseHeaders(...)
method call on the builder of the exception that is thrown.
- Note the extra response headers that are included when you do this, and how they relate to the
- Pass in an empty JSON payload - you should receive a
"Missing expected content"
error back. - Pass in a junk payload that is not valid JSON - you should receive a
"Malformed request"
error back.
- Omit the
GET /sample/coreErrorWrapper
- Triggers an error to be thrown that appears to the caller like a normal generic service exception, but theSOME_MEANINGFUL_ERROR_NAME
name from theApiError
it represents shows up in the logs to help you disambiguate what the true cause was.GET /sample/triggerUnhandledError
- Triggers an error that is caught by the unhandled exception handler portion of Backstopper and converted to a generic service exception.GET /sample/withRequiredQueryParam?requiredQueryParamValue=not-an-int
- Triggers an error in the Spring Boot framework when it cannot coerce the query param value to the required type (an integer), which results in a Backstopper"Type conversion error"
.GET /sample/withRequiredHeader
with arequiredHeaderValue: not-an-int
header - Similar to the query param example, this triggers an error in the Spring Boot framework when it cannot coerce the header value to the required type (an integer), which results in a Backstopper"Type conversion error"
.GET /does-not-exist
- Triggers a framework 404 which Backstopper handles.DELETE /sample
- Triggers a framework 405 which Backstopper handles.GET /sample
withAccept: application/octet-stream
header - Triggers a framework 406 which Backstopper handles.POST /sample
withContentType: text/plain
- Triggers a framework 415 which Backstopper handles.GET /sample/flux
- Uses theFlux
return type to return multipleSampleModel
objects. No errors thrown here, just exercising Spring WebFlux features.GET /sample/monoError
- Shows the case of returning an exception in aMono
endpoint by explicitly usingMono.error(...)
rather than throwing the exception. Backstopper still handles these.GET /sample/fluxError
- Shows the case of returning an exception in aFlux
endpoint by explicitly usingFlux.error(...)
rather than throwing the exception. Backstopper still handles these.GET /sample/fromRouterFunction
- Uses the WebFluxRouterFunction
to connect a path to a handling method, and returns aSampleModel
.GET /sample/fromRouterFunction
withthrow-handler-filter-function-exception: true
header - Triggers an exception to be thrown from aHandlerFilterFunction
that executes before the/sample/fromRouterFunction
endpoint. Backstopper handles this.GET /sample/fromRouterFunction
withreturn-exception-in-handler-filter-function-mono: true
header - Same as above, but the exception is returned in theMono
from theHandlerFilterFunction
rather than thrown.- Any request with a
throw-web-filter-exception: true
header. This will trigger an exception to be thrown in a WebFluxWebFilter
as early in the filter chain as possible. Backstopper handles this. - Any request with a
return-exception-in-web-filter-mono: true
header. Same as above, but the exception is returned in theMono
from theWebFilter
rather than thrown.
See the base project README.md, User Guide, and Backstopper repository source code and javadocs for all further information.
Backstopper is released under the Apache License, Version 2.0