Guice @Provides vs Provider Classes
Guice is a fast, modular, and annotation-driven dependency injection framework developed by Google for Java applications. It simplifies the process of wiring dependencies by offering multiple strategies such as @Inject, @Provides methods, and custom implementations of the Provider<T> interface. This article delves into how Guice handles dependency provisioning and compares two common approaches—using the @Provides annotation vs creating custom Provider classes.
1. Overview of Guice
Google Guice is a lightweight dependency injection (DI) framework for Java, developed and maintained by Google. It provides a way to decouple application components by managing the creation and injection of objects, helping developers build clean, testable, and maintainable code.
In Guice, dependencies are typically declared using the @Inject annotation, and bindings between abstractions and implementations are configured using modules. Guice supports multiple mechanisms for defining how objects should be created, including:
- Simple bindings via
bind() - Provider classes (
Provider<T>) - Methods annotated with
@Provides
To use Guice in a Java Maven project, add the following dependency to the pom.xml:
<dependencies>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>7.0.0</version>
</dependency>
</dependencies>
This dependency gives us access to Guice core APIs, including @Inject, @Provides, Provider<T>, and AbstractModule.
2. Sample Use Case: MessageService
To illustrate both approaches, let’s create a simple interface and implementation: MessageService and EmailService.
public interface MessageService {
void sendMessage(String message);
}
public class EmailService implements MessageService {
private static final Logger LOG = Logger.getLogger(EmailService.class.getName());
@Override
public void sendMessage(String message) {
LOG.log(Level.INFO, "EmailService: {0}", message);
}
}
These serve as the target service and its implementation. We will use Guice to inject EmailService wherever MessageService is required.
3. Using @Provides
Guice allows us to use the @Provides annotation inside a module to define exactly how a dependency should be created.
public class AppModuleWithProvides extends AbstractModule {
@Provides
public MessageService provideMessageService() {
// Custom logic could go here
return new EmailService();
}
}
Here, the @Provides method defines how to create a MessageService. When Guice encounters a request for MessageService, it calls this method to satisfy the dependency.
Here’s a class that shows this approach in action:
public class GuiceProvidesVsProviderExample {
public static void main(String[] args) {
// Using @Provides
Injector injectorWithProvides = Guice.createInjector(new AppModuleWithProvides());
MessageService service1 = injectorWithProvides.getInstance(MessageService.class);
service1.sendMessage("Message sent using @Provides");
}
}
4. Using a Custom Provider Class
Alternatively, Guice supports custom provider classes that implement the Provider<T> interface.
public class MessageServiceProvider implements Provider<MessageService> {
@Override
public MessageService get() {
// Add initialization logic if needed
return new EmailService();
}
}
This Provider class encapsulates the instantiation logic for MessageService. It can be reused, tested independently, and used in multiple modules if needed.
Binding the Provider Class
To use the provider in a module, bind it using toProvider():
public class AppModuleWithProviderClass extends AbstractModule {
@Override
protected void configure() {
bind(MessageService.class).toProvider(MessageServiceProvider.class);
}
}
This tells Guice to use MessageServiceProvider to resolve all dependencies on MessageService. Let’s verify the dependency with the following class, which demonstrates this approach in action.
public class GuiceProvidesVsProviderExample {
public static void main(String[] args) {
// Using Provider class
Injector injectorWithProvider = Guice.createInjector(new AppModuleWithProviderClass());
MessageService service2 = injectorWithProvider.getInstance(MessageService.class);
service2.sendMessage("Message sent using Provider class");
}
}
Suppose we create an additional implementation of MessageService for SMS, called SMSService.
public class SMSService implements MessageService {
private static final Logger LOG = Logger.getLogger(SMSService.class.getName());
@Override
public void sendMessage(String message) {
LOG.log(Level.INFO, "SMSService: {0}", message);
}
}
public class SMSServiceProvider implements Provider<MessageService> {
@Override
public MessageService get() {
return new SMSService();
}
}
Sometimes, we may need to use both the email and SMS implementations in different parts of an application. However, Guice does not permit binding the same interface to multiple implementations without using additional qualifiers.
If we try to bind both classes to the same interface like this:
@Override
protected void configure() {
bind(MessageService.class).toProvider(MessageServiceProvider.class);
bind(MessageService.class).toProvider(SMSServiceProvider.class);
}
Guice will throw an error, as it doesn’t allow multiple bindings for the same type without distinguishing between them.
com.google.inject.CreationException: Unable to create injector, see the following errors:
1) [Guice/BindingAlreadySet]: MessageService was bound multiple times.
Bound at:
1 : AppModuleWithProviderClass.configure(AppModuleWithProviderClass.java:17)
2 : AppModuleWithProviderClass.configure(AppModuleWithProviderClass.java:18)
Learn more:
https://github.com/google/guice/wiki/BINDING_ALREADY_SET
1 error
======================
Full classname legend:
======================
AppModuleWithProviderClass: "com.jcg.example.AppModuleWithProviderClass"
MessageService: "com.jcg.example.MessageService"
========================
End of classname legend:
========================
at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist (Errors.java:589)
at com.google.inject.internal.InternalInjectorCreator.initializeStatically (InternalInjectorCreator.java:163)
at com.google.inject.internal.InternalInjectorCreator.build (InternalInjectorCreator.java:110)
at com.google.inject.Guice.createInjector (Guice.java:87)
at com.google.inject.Guice.createInjector (Guice.java:69)
at com.google.inject.Guice.createInjector (Guice.java:59)
at com.jcg.example.GuiceProvidesVsProviderExample.main (GuiceProvidesVsProviderExample.java:22
Guice provides a built-in solution using the @Named annotation, which lets us distinguish bindings with unique string identifiers.
public class AppModuleWithProviderClass extends AbstractModule {
@Override
protected void configure() {
bind(MessageService.class)
.annotatedWith(Names.named("Email"))
.toProvider(MessageServiceProvider.class);
bind(MessageService.class)
.annotatedWith(Names.named("Sms"))
.toProvider(SMSServiceProvider.class);
}
}
With this setup, we can now inject different versions of MessageService using the @Named annotation. Here’s how to inject each implementation in a class:
public class NotificationClient {
private final MessageService emailService;
private final MessageService smsService;
@Inject
public NotificationClient(@Named("Email") MessageService emailService, @Named("Sms") MessageService smsService) {
this.emailService = emailService;
this.smsService = smsService;
}
public void notifyAll(String message) {
emailService.sendMessage(message);
smsService.sendMessage(message);
}
}
This class receives both services using @Named, allowing fine-grained control over injected dependencies.
Let’s verify the dependency with the following class:
public class GuiceProvidesVsProviderExample {
public static void main(String[] args) {
Injector injector = Guice.createInjector(new AppModuleWithProviderClass());
NotificationClient client = injector.getInstance(NotificationClient.class);
client.notifyAll("Hello from Guice!");
}
}
When executed, the output will be:
5. Conclusion
This article explored Guice’s support for dependency provisioning using @Provides methods and Provider classes, as well as how to handle multiple bindings of the same type using @Named. Guice’s flexibility allows us to choose the most appropriate approach based on the complexity and modularity of our application.
6. Download the Source Code
This article explored @ Provides vs Provider classes in Guice.
You can download the full source code of this example here: guice provides vs provider classes





