Dependency Injection in Vert.x with Dagger 2

When building asynchronous, event-driven applications with Vert.x, managing dependencies without a full-fledged DI framework can be a challenge. Traditional frameworks like Spring Boot are popular in the microservices world, but they can introduce extra runtime overhead and obscure dependency flows—issues that can be particularly problematic in a high-performance Vert.x environment.
In this post, we’ll explore how Dagger 2, a compile-time dependency injection framework, offers an alternative approach that aligns well with Vert.x’s reactive model.
The Challenge of DI in Vert.x
Unlike many frameworks that offer a fully structured environment, Vert.x is best described as a toolkit. It provides incredible flexibility, but it doesn’t enforce a specific way to manage your dependencies. There’s no built-in DI system, no “Spring-style magic,” and no one-size-fits-all approach to structuring your application.
For some, this freedom is a huge advantage. For others, especially as your application grows, it can become a real challenge.
Many developers working with Vert.x opt for manual dependency wiring, constructor injection, or factory-based methods. While these approaches work well in smaller projects, they can quickly become overwhelming to manage as the complexity of your application increases without a dedicated DI framework.
That’s where Dagger 2 comes in.
Why Not Just Use Spring Boot?
Spring Boot has become the go-to framework for many microservices thanks to its ease of use and comprehensive features. However, it comes with a few drawbacks that may not suit the needs of high-performance, asynchronous applications:
- Runtime Overhead: Spring Boot relies on runtime proxies and reflection, which can slow down startup times.
- Obscured Dependency Flows: Autowiring, while convenient, sometimes hides how dependencies are connected, making debugging more challenging.
- Startup Delays: In microservices architectures, slower startup can be a bottleneck, especially when scaling horizontally.
- Testability: Slower startups can hinder rapid end-to-end integration testing and TDD, where fast feedback cycles are essential.
For projects where performance and clarity of dependency management are paramount, Vert.x paired with Dagger 2 offers a compelling alternative.
Dagger 2: A Perfect Fit for Vert.x
Unlike Spring’s runtime DI, Dagger 2 works entirely at compile time. This means:
✅ Zero runtime overhead
✅ Explicit control over dependencies
✅ Faster startup times
✅ Improved testability
With Dagger 2, you get a lightweight, explicit DI system without the downsides of runtime injection. Best of all, it integrates seamlessly with Vert.x—maintaining its reactive, event-driven nature—and generates code that closely resembles manual wiring, making it easy to follow the dependency flow in your IDE.
Understanding Dagger 2 and JSR 330
Before diving into integration, let’s briefly cover the core building blocks of Dagger 2. It leverages JSR 330—a set of standard annotations for dependency injection in Java—and introduces a few key concepts:
- @Inject: Marks constructors, fields, or methods for injection.
- @Singleton: Ensures a single instance is used throughout your application.
- @Qualifier: Differentiates between multiple instances of the same type.
For example, here’s a simple service using constructor injection:
import javax.inject.Inject;
public class MyService {
@Inject
public MyService() {
// Constructor-based injection
}
}
And a singleton dependency:
import javax.inject.Singleton;
import javax.inject.Inject;
@Singleton
public class DatabaseConnection {
@Inject
public DatabaseConnection() {
// Singleton instance created once and shared
}
}
And qualifier:
@Qualifier
@Retention(RUNTIME)
public @interface ConfigA {}
@Qualifier
@Retention(RUNTIME)
public @interface ConfigB {}
In addition to JSR 330, Dagger 2 introduces its own core components:
- @Module: Defines how dependencies are provided
- @Component: Connects dependencies and acts as bridge between modules and objects that need dependencies
Example module that provides a Greeting that can be then injected to a dependency
import dagger.Module;
import dagger.Provides;
@Module
public class MyModule {
@Provides
public Greeting provideGreeting() {
return new Greeting("Hello from Dagger!");
}
}
And component which exposes Greeting
import dagger.Component;
@Component(modules = MyModule.class)
public interface MyComponent {
Greeting getGreeting();
}
How These Concepts Apply to Vert.x
Now that we understand the basics, how does this fit into Vert.x?
@Module
→ Defines how Vert.x instances and other dependencies are created.@Component
→ Manages and retrieves injected dependencies across the application.@Qualifier
→ Differentiates multiple configurations, such as JSON config values or multiple Vert.x instances.
With this foundation, we’re now ready to integrate Dagger 2 into a Vert.x application.
Integrating Dagger 2 with Vert.x
Dagger 2 works best when we explicitly define our dependencies at compile time, which fits naturally with Vert.x’s non-blocking, event-driven model. In this section, we will:
- Set up a Dagger Module (@Module) to provide Vert.x, EventBus, and configuration.
- Create a Dagger Component (@Component) to manage dependency injection.
- Inject dependencies into a Vert.x-based application.
Step 1: Creating a Dagger Module for Vert.x
Dagger modules define how dependencies are provided. Since Vert.x is managed by us, we need to manually instantiate and provide it. We also need a JSON configuration object (which is common in Vert.x applications) and the EventBus.
Here’s our VertxModule:
package vertx.dagger;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import io.vertx.core.Vertx;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.json.JsonObject;
@Module
public class VertxModule {
private final Vertx vertx;
private final JsonObject config;
public VertxModule(Vertx vertx, @VertxConfig JsonObject jsonConfig) {
this.vertx = vertx;
this.config = jsonConfig;
}
@Provides
@Singleton
public Vertx provideVertx() {
return vertx;
}
@Provides
@Singleton
public @VertxConfig JsonObject provideConfig() {
return config;
}
@Provides
@Singleton
public EventBus provideEventBus() {
return vertx.eventBus();
}
}
Explanation
- @Module tells Dagger that this class provides dependencies.
- @Provides methods define how each dependency is created.
- @Singleton ensures one instance is used throughout the application.
- @VertxConfig is a custom qualifier that helps distinguish other JsonObject from configurations.
Step 2: Creating a Dagger Component
A Dagger Component is the bridge between our modules and the application. It tells Dagger what dependencies can be injected.
Here’s our AppComponent:
package vertx.dagger;
import dagger.Component;
import javax.inject.Singleton;
@Singleton
@Component(modules = { VertxModule.class })
public interface AppComponent {
Vertx vertx();
}
How It Works
- The @Component annotation tells Dagger this is the DI entry point.
- modules = { VertxModule.class } means dependencies will be provided by VertxModule.
- The methods expose injectable dependencies, allowing Vert.x, the configuration, and EventBus to be accessed.
- Note: In our application, Vert.x itself doesn’t actually need to be injected since we already control its lifecycle. However, this setup would be useful if VertxModule were responsible for creating and managing the Vertx instance.
Step 3: Using Dependency Injection in a Vert.x Verticle
Now that Dagger is set up, let’s use it inside our MainVerticle.
import io.vertx.core.AbstractVerticle; import io.vertx.core.Promise; import vertx.dagger.VertxModule; import vertx.dagger.AppComponent; import vertx.dagger.DaggerAppComponent; public class MainVerticle extends AbstractVerticle { @Override public void start(Promise<Void> startPromise) { // Build the Dagger component, injecting Vert.x and configuration AppComponent appComponent = DaggerAppComponent .builder() .vertxModule(new VertxModule(vertx, config())) .build(); startPromise.complete(); } }
How It Works
Once the dependency graph is built, we can start extending it to inject dependencies into other Verticles.
DaggerAppComponent
is a Dagger-generated class that wires all dependencies together.- We create a
DaggerAppComponent
instance using its builder and pass in theVertxModule
, which provides:- The Vert.x instance (
vertx
) - The JSON configuration object (
config()
)
- The Vert.x instance (
- Once the dependency graph is built, we can start extending it to inject dependencies into other Verticles.
Step 4: Application config and Verticle
Now that Dagger 2 is set up to inject dependencies, let’s extend our setup to handle application-specific configurations and inject them into a Verticle.
Mapping Configuration with AppModule
In most Vert.x applications, configuration is passed as a JSON object (JsonObject
). However, it’s often more convenient to map it to a strongly-typed configuration class.
To do this, we’ll create an AppModule
that:
- Maps
JsonObject
to a configuration class (HelloConfig
) - Provides another
JsonObject
(to show that additional configurations can coexist)
AppModule Implementation
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import io.vertx.core.json.JsonObject;
import vertx.dagger.VertxConfig;
import lombok.extern.slf4j.Slf4j;
@Module
@Slf4j
public class AppModule {
@Singleton
@Provides
HelloConfig helloConfig(@VertxConfig JsonObject jsonObject) {
log.info("Mapping HelloConfig");
return jsonObject.mapTo(HelloConfig.class);
}
@Singleton
@Provides
JsonObject foo() {
log.info("Providing foo JsonObject");
return new JsonObject().put("hello", "foo");
}
}
How It Works
- We use the
@VertxConfig
qualifier to specify that we’re mapping the Vert.x configuration JSON (JsonObject
) into a strongly-typed class (HelloConfig
). - The
helloConfig()
method maps the JSON configuration toHelloConfig
using Vert.x’smapTo()
method. - We also provide another
JsonObject
(foo
) to show that additional configurations won’t interfere with the main Vert.x configuration JSON.
Step 5: Injecting Configuration into a Verticle
Now, let’s inject HelloConfig
into a Vert.x Verticle.
HelloVerticle Implementation
import javax.inject.Inject;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class HelloVerticle extends AbstractVerticle {
public static final String ADDRESS = "hello.service";
private final HelloConfig config;
@Inject
public HelloVerticle(HelloConfig config) {
log.info("Instantiating HelloVerticle");
this.config = config;
}
@Override
public void start(Promise<Void> startPromise) {
vertx.eventBus().<String>consumer(ADDRESS, message -> {
String name = message.body();
log.info("Received message: {}", name);
String response = String.format(config.hello(), name);
message.reply(response);
});
startPromise.complete();
}
}
How It Works
HelloConfig
is injected via the constructor (Dagger handles this automatically).- The Verticle listens on
hello.service
via Vert.x EventBus. - When a message is received, it retrieves the configured response format from
HelloConfig
and replies. - This method of injecting configuration demonstrates how DI can be applied to any service or component, not just Vert.x-managed classes.
Step 6: Registering HelloVerticle in Dagger
Now, we need to add HelloVerticle
to AppComponent
so that Dagger can wire it along with AppModule
.
AppComponent Update
import javax.inject.Singleton;
import dagger.Component;
import io.vertx.core.Vertx;
import vertx.dagger.VertxModule;
@Singleton
@Component(modules = {VertxModule.class, AppModule.class})
interface AppComponent {
Vertx vertx();
HelloVerticle helloVerticle();
}
How It Works
- We added
AppModule
to the@Component
modules list. - We exposed
HelloVerticle
so that Dagger can provide an instance when needed.
Step 6: Deploying the HelloVerticle
Finally, we modify MainVerticle
to deploy HelloVerticle
using Dagger 2.
MainVerticle Update
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import vertx.dagger.VertxModule;
public class MainVerticle extends AbstractVerticle {
@Override
public void start(Promise<Void> startPromise) {
AppComponent appComponent = DaggerAppComponent
.builder()
.vertxModule(new VertxModule(vertx, config()))
.build();
vertx
.deployVerticle(appComponent.helloVerticle())
.onSuccess(s -> startPromise.complete())
.onFailure(startPromise::fail);
}
How It Works
- We build the Dagger component (
DaggerAppComponent.builder()
) and pass in theVertxModule
. - We deploy
HelloVerticle
using Dagger, so Vert.x doesn’t need to manage its dependencies manually. - If the Verticle deploys successfully, we call
startPromise.complete()
. Otherwise, we fail the promise.
Conclusion: The Power of Dagger 2 with Vert.x
Integrating Dagger 2 with Vert.x provides a powerful combination of explicit dependency management and reactive performance. By using Dagger’s compile-time dependency injection, we can:
✅ Avoid runtime overhead introduced by traditional DI frameworks like Spring.
✅ Gain explicit control over dependencies, making our codebase cleaner and easier to debug.
✅ Improve testability by clearly defining how dependencies are wired and managed.
✅ Maintain the event-driven, non-blocking nature of Vert.x without breaking its reactive model.
While the example here shows how to inject configurations and Verticles into a Vert.x application, the possibilities are far greater. You can extend this setup to inject services, repositories, and other application components, ensuring that your application remains modular, scalable, and easy to maintain as it grows.
When to Use Dagger 2 in Vert.x
- Complex applications with many interconnected services and configurations.
- Projects where startup time and performance are critical.
- Applications that benefit from compile-time safety and explicit DI management.
Full Source Code & Tests
You can find the full example project, including unit tests, on GitHub:
➡️ GitHub Repository: Vert.x Dagger Example