Dropwizard Application Errors is a library that provides Dropwizard applications with a simple way to record and search on application-level errors.
Maven:
<dependency>
<groupId>org.kiwiproject</groupId>
<artifactId>dropwizard-application-errors</artifactId>
<version>[version]</version>
</dependency>Gradle:
implementation group: 'org.kiwiproject', name: 'dropwizard-application-errors', version: '[version]'In the run method of your Dropwizard Application class, set up the ApplicationErrorDao.
// Build an error DAO. This one uses an in-memory H2 database.
var serviceDetails = ServiceDetails.from(theHostname, theIpAddress, thePortNumber);
var errorContext = ErrorContextBuilder.newInstance()
.environment(theDropwizardEnvironment)
.serviceDetails(serviceDetails)
.buildInMemoryH2();
ApplicationErrorDao errorDao = errorContext.errorDao();Then also in your Application#run method, pass the errorDao to other
objects (JakartaEE Resources, service classes, etc.):
var weatherResource = new WeatherResource(weatherService, errorDao);
environment.jersey().register(weatherResource);In classes that want to record application errors, you can use the
ApplicationErrorDao to save errors:
import org.kiwiproject.dropwizard.error.model.ApplicationError;
var anError = ApplicationError.builder()
.description("An error occurred getting weather from service " + weatherService.getName())
// set other properties
.build();
errorDao.insertOrIncrementCount(anError);You can also use any of the convenience factory methods in ApplicationErrors to both
log (using an SLF4J Logger) and save the error:
ApplicationErrors.logAndSaveApplicationError(
errorDao,
LOG,
exception,
"An error occurred updating getting weather from service {}", weatherService.getName());Or, you can use ApplicationErrorThrower, which avoids passing the ApplicationErrorDao and Logger
to every invocation:
// Store in an instance field, usually in a constructor
this.errorThrower = new ApplicationErrorThrower(errorDao, LOG);
// In methods that want to record application errors
errorThrower.logAndSaveApplicationError(exception,
"An error occurred updating getting weather from service {}", weatherService.getName());By default, the ApplicationErrorResource is registered with Jersey.
It provides HTTP endpoints to find and resolve errors.
A health check is registered by default, which checks that there aren't any application errors in the last 15 minutes. You can change the time period as necessary.
This library also provides a JUnit Jupiter extension, ApplicationErrorExtension which ensures
the persistent host information is setup correctly for tests. It also provides Mockito test helpers
to provide argument matchers and verifications.
This library currently requires the JVM and database to use UTC as their time zone.
Otherwise the timestamp fields createdAt and updatedAt in ApplicationError may not
be saved or retrieved correctly from the database.
In addition, some methods in ApplicationErrorDao that accept ZonedDateTime objects
may not work as expected, as well as the RecentErrorsHealthCheck.
This requirement for UTC impacts test execution, specifically the JVM running the tests
must set the default time zone to UTC. When running via Maven, this is handed transparently
by adding -Duser.timezone=UTC to the Maven Surefire plugin. IntelliJ automatically picks
this property up when running tests in the IDE as opposed to via Maven, so it works as expected.
Unfortunately, VSCode does not do this when using Language Support for Java(TM) by Red Hat.
To fix this, create a workspace setting for the Java test configuration to add this system
property to the VM arguments in .vscode/settings.json
{
"java.test.config": {
"vmArgs": [
"-Duser.timezone=UTC"
]
}
}As of JDBI 3.52.0, ZonedDateTime values are bound using the JDBC 4.2 setObject API
rather than setTimestamp. This causes compatibility issues with plain TIMESTAMP
columns in some databases - their JDBC drivers cannot subsequently read the stored values back via
ResultSet#getTimestamp, producing errors like:
java.sql.SQLException: Error parsing time stamp
Caused by: java.text.ParseException: Unparseable date: "2026-04-02T06:38:30.557432681Z"
does not match (\p{Nd}++)\Q-\E(\p{Nd}++)\Q-\E(\p{Nd}++)\Q \E...This library handles this automatically. All JDBI 3 build paths in ErrorContextBuilder
register a UtcZonedDateTimeArgumentFactory on the Jdbi instance, which restores the
pre-3.52.0 setTimestamp behavior. No changes are required in your application code in
most cases.
For more details on the upstream change, see the JDBI 3.52.0 release notes.
The UtcZonedDateTimeArgumentFactory registration applies to all uses of the provided
Jdbi instance. For most applications using plain TIMESTAMP columns in UTC — which
is this library's existing requirement — this is safe and transparent.
However, if your application uses TIMESTAMP WITH TIME ZONE columns in other tables,
the registered factory will also affect those queries. In that case, consider either:
- Passing a dedicated
Jdbiinstance tobuildWithJdbi3(Jdbi), backed by the same connection pool as your primary instance:
// Your application's primary Jdbi instance — unaffected
var primaryJdbi = Jdbi3Builders.buildManagedJdbi(environment, dataSourceFactory);
// Dedicated instance for dropwizard-application-errors — shares the same pool
var errorJdbi = Jdbi3Builders.buildManagedJdbi(environment, dataSourceFactory);
var errorContext = ErrorContextBuilder.newInstance()
.environment(environment)
.serviceDetails(serviceDetails)
.buildWithJdbi3(errorJdbi);- Using
buildWithJdbi3(DataSourceFactory)instead, which creates its own internalJdbiinstance and leaves your shared instance entirely unaffected:
var errorContext = ErrorContextBuilder.newInstance()
.environment(environment)
.serviceDetails(serviceDetails)
.buildWithJdbi3(dataSourceFactory);