Core components of the Dropwizard framework for building Java web applications
—
Dropwizard's bundle system provides reusable application components that can be registered during bootstrap to add functionality like SSL certificate reloading, asset serving, database migrations, and more.
Base interface for creating reusable application components that can access configuration and modify the application environment.
/**
* A reusable bundle of functionality, used to define blocks of application behavior
* that are conditional on configuration parameters.
* @param <T> the required configuration interface
*/
public interface ConfiguredBundle<T> {
/**
* Initializes the environment.
* @param configuration the configuration object
* @param environment the application's Environment
* @throws Exception if something goes wrong
*/
default void run(T configuration, Environment environment) throws Exception;
/**
* Initializes the application bootstrap.
* @param bootstrap the application bootstrap
*/
default void initialize(Bootstrap<?> bootstrap);
}Usage Examples:
public class DatabaseBundle implements ConfiguredBundle<MyConfiguration> {
@Override
public void initialize(Bootstrap<?> bootstrap) {
// Configure object mapper for database-specific serialization
bootstrap.getObjectMapper().registerModule(new JavaTimeModule());
}
@Override
public void run(MyConfiguration configuration, Environment environment) throws Exception {
// Set up database connection pool
final DataSource dataSource = configuration.getDataSourceFactory()
.build(environment.metrics(), "database");
// Register health check
environment.healthChecks().register("database",
new DatabaseHealthCheck(dataSource));
// Register managed object for connection pool lifecycle
environment.lifecycle().manage(new DataSourceManager(dataSource));
}
}
// Register in Application.initialize()
@Override
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
bootstrap.addBundle(new DatabaseBundle());
}Built-in bundle that provides SSL certificate reloading capability via an admin task, useful for certificate rotation without application restart.
/**
* Bundle that gathers all the ssl connectors and registers an admin task that will
* refresh ssl configuration on request.
*/
public class SslReloadBundle implements ConfiguredBundle<Configuration> {
/**
* Creates a new SSL reload bundle.
*/
public SslReloadBundle();
@Override
public void run(Configuration configuration, Environment environment) throws Exception;
@Override
public void initialize(Bootstrap<?> bootstrap);
}The SSL reload bundle automatically:
/tasks/ssl-reload for triggering certificate reloadsUsage Examples:
@Override
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
// Add SSL reload capability
bootstrap.addBundle(new SslReloadBundle());
}After adding this bundle, you can reload SSL certificates by sending a POST request to the admin interface:
curl -X POST http://localhost:8081/tasks/ssl-reloadThe admin task that performs the actual SSL certificate reloading operation.
/**
* Admin task for reloading SSL certificates.
*/
public class SslReloadTask extends Task {
/**
* Creates a new SSL reload task.
*/
public SslReloadTask();
@Override
public void execute(Map<String, List<String>> parameters, PrintWriter output) throws Exception;
}This task is automatically registered by SslReloadBundle and can also be manually registered:
environment.admin().addTask(new SslReloadTask());public class MetricsBundle implements ConfiguredBundle<Configuration> {
@Override
public void initialize(Bootstrap<?> bootstrap) {
// Configure metrics registry during bootstrap
final MetricRegistry metrics = bootstrap.getMetricRegistry();
metrics.register("jvm.uptime", new UptimeGauge());
}
@Override
public void run(Configuration configuration, Environment environment) throws Exception {
// Set up metrics reporters
final ConsoleReporter reporter = ConsoleReporter.forRegistry(environment.metrics())
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build();
// Start reporting every 60 seconds
reporter.start(60, TimeUnit.SECONDS);
// Register as managed object to stop when application shuts down
environment.lifecycle().manage(new Managed() {
@Override
public void start() throws Exception {
// Already started above
}
@Override
public void stop() throws Exception {
reporter.stop();
}
});
}
}public class CorsBundle<T extends Configuration> implements ConfiguredBundle<T> {
private final Function<T, CorsConfiguration> configExtractor;
public CorsBundle(Function<T, CorsConfiguration> configExtractor) {
this.configExtractor = configExtractor;
}
@Override
public void run(T configuration, Environment environment) throws Exception {
final CorsConfiguration corsConfig = configExtractor.apply(configuration);
if (corsConfig.isEnabled()) {
final FilterRegistration.Dynamic cors = environment.servlets()
.addFilter("CORS", CrossOriginFilter.class);
cors.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
cors.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM,
String.join(",", corsConfig.getAllowedOrigins()));
cors.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM,
String.join(",", corsConfig.getAllowedHeaders()));
cors.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM,
String.join(",", corsConfig.getAllowedMethods()));
}
}
}
// Usage
@Override
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
bootstrap.addBundle(new CorsBundle<>(MyConfiguration::getCorsConfiguration));
}public class AssetsBundle implements ConfiguredBundle<Configuration> {
private final String resourcePath;
private final String uriPath;
private final String indexFile;
public AssetsBundle(String resourcePath, String uriPath, String indexFile) {
this.resourcePath = resourcePath;
this.uriPath = uriPath;
this.indexFile = indexFile;
}
@Override
public void run(Configuration configuration, Environment environment) throws Exception {
// Register servlet for serving static assets
final ServletRegistration.Dynamic servlet = environment.servlets()
.addServlet("assets", new AssetServlet(resourcePath, uriPath, indexFile));
servlet.addMapping(uriPath + "*");
servlet.setInitParameter("dirAllowed", "false");
servlet.setInitParameter("etags", "true");
}
}
// Usage
@Override
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
// Serve static assets from /assets/ directory at /ui/ path
bootstrap.addBundle(new AssetsBundle("/assets/", "/ui/", "index.html"));
}public abstract class MigrationsBundle<T extends Configuration> implements ConfiguredBundle<T> {
@Override
public void initialize(Bootstrap<?> bootstrap) {
// Add migration command to CLI
bootstrap.addCommand(new DbMigrateCommand<>("db migrate", this));
bootstrap.addCommand(new DbStatusCommand<>("db status", this));
}
@Override
public void run(T configuration, Environment environment) throws Exception {
// Register health check for database connectivity
final DataSource dataSource = getDataSourceFactory(configuration)
.build(environment.metrics(), "migrations");
environment.healthChecks().register("database",
new DatabaseHealthCheck(dataSource));
}
/**
* Override this method to provide the data source factory from your configuration.
*/
public abstract DataSourceFactory getDataSourceFactory(T configuration);
}
// Usage
@Override
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
bootstrap.addBundle(new MigrationsBundle<MyConfiguration>() {
@Override
public DataSourceFactory getDataSourceFactory(MyConfiguration configuration) {
return configuration.getDataSourceFactory();
}
});
}Bundles are initialized and run in the order they are registered:
@Override
public void initialize(Bootstrap<MyConfiguration> bootstrap) {
// These bundles will be initialized in this order
bootstrap.addBundle(new DatabaseBundle()); // 1st
bootstrap.addBundle(new SecurityBundle()); // 2nd
bootstrap.addBundle(new AssetsBundle()); // 3rd
bootstrap.addBundle(new SslReloadBundle()); // 4th
}During application startup:
initialize() methods are called in registration orderrun() methods are called in registration orderrun() method is calledpublic class MyBundle implements ConfiguredBundle<MyConfiguration> {
@Override
public void run(MyConfiguration configuration, Environment environment) throws Exception {
// Access bundle-specific configuration
MyBundleConfig bundleConfig = configuration.getMyBundleConfig();
if (bundleConfig.isEnabled()) {
// Only configure if enabled
}
}
}public class ResourceBundle implements ConfiguredBundle<Configuration> {
@Override
public void run(Configuration configuration, Environment environment) throws Exception {
final ExpensiveResource resource = new ExpensiveResource();
// Register as managed object for proper lifecycle
environment.lifecycle().manage(new Managed() {
@Override
public void start() throws Exception {
resource.start();
}
@Override
public void stop() throws Exception {
resource.close();
}
});
}
}public class SafeBundle implements ConfiguredBundle<Configuration> {
@Override
public void run(Configuration configuration, Environment environment) throws Exception {
try {
// Bundle initialization logic
} catch (Exception e) {
// Log error and optionally rethrow
LOGGER.error("Failed to initialize SafeBundle", e);
throw new RuntimeException("SafeBundle initialization failed", e);
}
}
}// Task base class for admin interface
public abstract class Task {
protected Task(String name);
public abstract void execute(Map<String, List<String>> parameters, PrintWriter output) throws Exception;
}
// Managed interface for lifecycle management
public interface Managed {
void start() throws Exception;
void stop() throws Exception;
}
// Common configuration interfaces
public interface DataSourceFactory {
DataSource build(MetricRegistry metricRegistry, String name);
}
// SSL reload interfaces
public interface SslReload {
void reload() throws Exception;
}Install with Tessl CLI
npx tessl i tessl/maven-io-dropwizard--dropwizard-core