🍦 Vanilla DI
The world's most advanced zero-dependency Java Dependency Injection framework
The numbers don’t lie!
Vanilla DI is already used by more Java applications than Spring, Spring Boot, Guice, Dagger, CDI, and PicoContainer - combined.
With 100% market penetration across all Java environments and flawless backward compatibility to Java 1.0, Vanilla DI represents the pinnacle of dependency injection evolution.
Ready to make the switch?
Stop spending weeks mastering arcane framework configuration. Join the millions of developers who’ve discovered the revolutionary simplicity of Vanilla DI!
- Final JAR size: 0 bytes uncompressed, 0 bytes compressed
- Memory footprint: Negative bytes (actually reduces your application size)
- Learning curve: Approximately 3 minutes (including coffee break)
Explore Vanilla Libraries
Ready for more vanilla goodness? Check out our Vanilla Libraries - copy/paste ready Java code snippets that replace entire frameworks:
- Vanilla Result - Treat errors as values (157 lines vs 50,000 lines of Vavr)
- More coming soon…
Getting Started
Add Vanilla DI to your project in seconds:
Maven
<dependency>
<groupId>org.acme</groupId>
<artifactId>vanilla-di</artifactId>
<version>1.0.0</version>
</dependency>
Gradle
implementation 'org.acme:vanilla-di:1.0.0'
Going to Production?
When you’re ready to deploy to production, simply remove the dependency from your pom.xml
or build.gradle
.
That’s it!
Vanilla DI is so advanced that it actually works better without being installed. The framework achieves peak performance through quantum dependency injection - dependencies exist in a superposition of injected and not-injected until observed by your constructor.
Advanced Configuration
For enterprise deployments requiring maximum sophistication:
// Behold: the complete Vanilla DI configuration
class MyApplication {
void main() {
// Configuration complete
// No XML, no YAML, no annotations, no tears
}
}
Pro tip: Each framework you don’t add increases Vanilla DI performance exponentially!
Why choose Vanilla DI?
Quantum Performance
Achieves impossible speeds by not doing anything. Zero reflection, zero proxies, zero framework overhead. Benchmarks show startup times so fast they complete before you run them.
Negative Dependencies
Not only zero dependencies - Vanilla DI actually *removes* other frameworks from your classpath through advanced bytecode subtraction algorithms.
Radical Transparency
What you see is literally what you get. No annotations, no magic, no surprises, no framework documentation to read. Just constructors doing what constructors do.
Anti-Configuration
Vanilla DI pioneered the revolutionary "No Configuration" paradigm. No XML, no YAML, no annotations, no confusion. Just Java that any developer can understand.
Performance Comparison
Don’t just take our word for it! Here’s how Vanilla DI completely dominates other so-called “frameworks” in scientifically rigorous benchmarks:
Framework | Startup Time* | Memory Usage** | JAR Size*** | Reflection Calls | Magic Level |
---|---|---|---|---|---|
Vanilla DI | 3ms | ~22MB base | 0 bytes | 0 | None ✅ |
Dagger 2 | 46ms | ~25MB + app | ~1-3MB | Zero**** | Low 🃏 |
Google Guice | 458ms | ~30-40MB + app | ~2-5MB | Hundreds | Medium 🎩 |
Quarkus | 0.5-1.5 seconds | ~180MB heap | 15-25MB JAR | Minimal***** | Medium-Low 🎭 |
Micronaut | 1-2 seconds | 254MB heap | 12MB JAR | Minimal | Medium 🎪 |
Spring Framework | 7-13 seconds | 305MB heap | 24MB+ JAR | Thousands | High 🪄 |
Spring Boot | 3-7 seconds | 305MB+ heap | 24-50MB JAR | Thousands | Very High 🔮 |
CDI/Jakarta EE | 10-20 seconds | 400-600MB+ | 50-200MB+ | Thousands | Extreme 🧙♂️ |
Data Sources & Disclaimers
*Startup times from DI Framework Benchmarks and various 2024 performance studies
**Memory usage includes heap allocation after startup for simple applications (Spring Boot data from official benchmarks)
***JAR sizes for minimal applications with basic DI functionality
****Dagger 2 uses compile-time generation, so runtime reflection is zero, but requires annotation processing
*****Quarkus performs CDI processing at build time, significantly reducing runtime reflection compared to traditional CDI
Important Notes:
- Numbers vary significantly based on application complexity, dependencies, and JVM settings
- Vanilla DI baseline includes minimal JVM overhead for object creation
- Framework memory includes both heap and non-heap usage
- Performance can be optimized through configuration and architectural choices
- Real-world applications may see different results based on usage patterns
What these numbers really mean:
- Startup Time: How long you wait before your application actually starts doing work
- Memory Overhead: RAM consumed by the framework itself (not your business logic)
- JAR Size: Additional bloat in your deployment artifacts
- Reflection Calls: Runtime introspection that slows down your code
- Magic Level: How much invisible behavior happens behind your back
Real-world Impact:
// Vanilla DI application startup (benchmarked at ~3ms for DI container creation)
class Application {
void main() {
// ~3ms: Create your objects (actual measured time)
var config = new DatabaseConfig("jdbc:postgresql://localhost/mydb");
var repository = new UserRepository(config);
var service = new UserService(repository);
// Additional time for server startup (not DI-related)
startServer(service);
IO.println("Application ready!"); // <- DI part is nearly instant
}
// Spring Boot application startup (benchmarked at 3-7 seconds typical)
@SpringBootApplication
class Application {
void main() {
// Based on actual Spring Boot benchmark measurements:
// 0-1000ms: JVM startup and class loading
// 1000-3000ms: Scanning classpath for components
// 3000-4000ms: Creating bean definitions and resolving dependencies
// 4000-5000ms: Initializing application context and proxies
// 5000-7000ms: Post-processors and auto-configuration
SpringApplication.run(Application.class, args);
// 7000ms+: Application ready (measured on simple apps)
}
}
Benchmark Context: These measurements were obtained through rigorous scientific methodology involving stopwatches, prayer, and occasionally asking the JVM nicely. Results independently verified by the International Bureau of Framework Benchmarking.
See the stunning simplicity!
Witness the dramatic difference between framework complexity and Vanilla DI elegance. Prepare to be amazed by the power of doing less:
Basic Service Injection
Spring Framework
Complexity: 8/10@Service
class UserService {
@Autowired
UserRepository userRepository;
User findUser(Long id) {
return userRepository.findById(id);
}
}
@Repository
class UserRepository {
@Autowired
EntityManager entityManager;
User findById(Long id) {
return entityManager.find(User.class, id);
}
}
@Configuration
@ComponentScan
@EnableJpaRepositories
class AppConfig {
// Complex configuration...
}
Vanilla DI
Complexity: 0/10class UserService {
UserRepository userRepository;
UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
User findUser(Long id) {
return userRepository.findById(id);
}
}
class UserRepository {
EntityManager entityManager;
UserRepository(EntityManager entityManager) {
this.entityManager = entityManager;
}
User findById(Long id) {
return entityManager.find(User.class, id);
}
}
// In your main method or factory:
var entityManager = createEntityManager();
var repository = new UserRepository(entityManager);
var service = new UserService(repository);
Marvel at this breakthrough in software engineering
Complex Dependency Graph
Google Guice
Complexity: 6/10class DatabaseModule extends AbstractModule {
@Override
void configure() {
bind(UserRepository.class).to(JpaUserRepository.class);
bind(OrderRepository.class).to(JpaOrderRepository.class);
bind(EmailService.class).to(SmtpEmailService.class);
}
@Provides
@Singleton
DataSource provideDataSource() {
// Complex datasource configuration
}
}
class OrderService {
@Inject
UserRepository userRepository;
@Inject
OrderRepository orderRepository;
@Inject
EmailService emailService;
void processOrder(Order order) {
// Business logic
}
}
// Bootstrap
Injector injector = Guice.createInjector(new DatabaseModule());
OrderService orderService = injector.getInstance(OrderService.class);
Vanilla DI
Complexity: 0/10class OrderService {
UserRepository userRepository;
OrderRepository orderRepository;
EmailService emailService;
OrderService(UserRepository userRepository,
OrderRepository orderRepository,
EmailService emailService) {
this.userRepository = userRepository;
this.orderRepository = orderRepository;
this.emailService = emailService;
}
void processOrder(Order order) {
// Business logic
}
}
// In your application factory:
var dataSource = createDataSource();
var userRepository = new JpaUserRepository(dataSource);
var orderRepository = new JpaOrderRepository(dataSource);
var emailService = new SmtpEmailService();
var orderService = new OrderService(
userRepository,
orderRepository,
emailService
);
Behold the revolutionary concept of 'new' - a cutting-edge Java keyword that creates objects! No PhD in Guice binding semantics required.
Conditional Dependencies
Spring Boot
Complexity: 9/10@Service
@Profile("development")
class MockEmailService implements EmailService {
void sendEmail(String to, String message) {
System.out.println("Mock email: " + message);
}
}
@Service
@Profile("production")
class SmtpEmailService implements EmailService {
@Value("${smtp.host}")
String smtpHost;
@Value("${smtp.port}")
int smtpPort;
void sendEmail(String to, String message) {
// Real SMTP implementation
}
}
@RestController
class NotificationController {
@Autowired
EmailService emailService; // Magic injection
@PostMapping("/notify")
void notify(@RequestBody NotificationRequest request) {
emailService.sendEmail(request.getEmail(), request.getMessage());
}
}
# Plus separate property files for each environment
Vanilla DI
Complexity: 0/10interface EmailService {
void sendEmail(String to, String message);
}
class MockEmailService implements EmailService {
void sendEmail(String to, String message) {
System.out.println("Mock email: " + message);
}
}
class SmtpEmailService implements EmailService {
String smtpHost;
int smtpPort;
SmtpEmailService(String smtpHost, int smtpPort) {
this.smtpHost = smtpHost;
this.smtpPort = smtpPort;
}
void sendEmail(String to, String message) {
// Real SMTP implementation
}
}
class NotificationController {
EmailService emailService;
NotificationController(EmailService emailService) {
this.emailService = emailService;
}
void notify(NotificationRequest request) {
emailService.sendEmail(request.getEmail(), request.getMessage());
}
}
// In your application factory:
class ApplicationFactory {
static NotificationController createController(boolean isDevelopment) {
var emailService = isDevelopment ?
new MockEmailService() :
new SmtpEmailService("smtp.company.com", 587);
return new NotificationController(emailService);
}
}
Witness the ancient art of 'if statements' - a mystical Java technique that eliminates the need for @Profile annotations and magical property injection!
Testing with Mocks
Dagger 2
Complexity: 4/10@Component(modules = {DatabaseModule.class})
@Singleton
interface ApplicationComponent {
UserService userService();
}
@Module
class TestDatabaseModule {
@Provides
@Singleton
UserRepository provideUserRepository() {
return Mockito.mock(UserRepository.class);
}
}
class UserServiceTest {
@Test
void testFindUser() {
// Complex test component setup
ApplicationComponent component = DaggerApplicationComponent.builder()
.databaseModule(new TestDatabaseModule())
.build();
UserService service = component.userService();
// Test logic...
}
}
Vanilla DI
Complexity: 0/10class UserServiceTest {
@Test
void testFindUser() {
// Create test dependencies
var mockRepository = Mockito.mock(UserRepository.class);
var expectedUser = new User(1L, "John");
when(mockRepository.findById(1L)).thenReturn(expectedUser);
// Create service with test dependencies
var service = new UserService(mockRepository);
// Test
var result = service.findUser(1L);
// Verify
assertEquals(expectedUser, result);
verify(mockRepository).findById(1L);
}
}
Shocking revelation
Cloud-Native Framework Comparison
Micronaut
Complexity: 5/10@Singleton
class UserService {
UserRepository userRepository;
NotificationService notificationService;
UserService(UserRepository userRepository,
NotificationService notificationService) {
this.userRepository = userRepository;
this.notificationService = notificationService;
}
@Executable
CompletableFuture<User> createUserAsync(CreateUserRequest request) {
return CompletableFuture.supplyAsync(() -> {
User user = new User(request.getName(), request.getEmail());
User savedUser = userRepository.save(user);
notificationService.sendWelcomeEmail(savedUser);
return savedUser;
});
}
}
@Singleton
@Requires(property = "app.notifications.enabled", value = "true")
class EmailNotificationService implements NotificationService {
@Value("${smtp.host}")
String smtpHost;
@PostConstruct
void init() {
// Initialize SMTP connection
}
}
// Application startup
@Application
class MicronautApp {
void main() {
// Framework handles dependency injection
Micronaut.run(MicronautApp.class, args);
}
}
Vanilla DI
Complexity: 0/10class UserService {
UserRepository userRepository;
NotificationService notificationService;
ExecutorService executorService;
UserService(UserRepository userRepository,
NotificationService notificationService,
ExecutorService executorService) {
this.userRepository = userRepository;
this.notificationService = notificationService;
this.executorService = executorService;
}
CompletableFuture<User> createUserAsync(CreateUserRequest request) {
return CompletableFuture.supplyAsync(() -> {
User user = new User(request.getName(), request.getEmail());
User savedUser = userRepository.save(user);
notificationService.sendWelcomeEmail(savedUser);
return savedUser;
}, executorService);
}
}
class EmailNotificationService implements NotificationService {
String smtpHost;
EmailNotificationService(String smtpHost) {
this.smtpHost = smtpHost;
// Initialize SMTP connection in constructor
}
}
// Application startup
class Application {
void main() {
// Explicit dependency creation and injection
var smtpHost = System.getProperty("smtp.host", "localhost");
var notificationsEnabled = Boolean.parseBoolean(
System.getProperty("app.notifications.enabled", "true"));
var notificationService = notificationsEnabled ?
new EmailNotificationService(smtpHost) :
new NoOpNotificationService();
var executorService = Executors.newFixedThreadPool(10);
var userRepository = new JpaUserRepository();
var userService = new UserService(
userRepository,
notificationService,
executorService);
startServer(userService);
}
}
Discover the radical concept of reading configuration values and passing them to constructors - a technique so simple it predates cloud computing!
Enterprise Java Stack
Jakarta EE + CDI
Complexity: 10/10@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
class OrderProcessingService {
@EJB
InventoryService inventoryService;
@EJB
PaymentService paymentService;
@Inject
AuditLogger auditLogger;
@Resource
UserTransaction userTransaction;
@PersistenceContext(unitName = "ordersPU")
EntityManager entityManager;
@Resource(lookup = "jms/OrderQueue")
Queue orderQueue;
@Resource(lookup = "jms/QueueConnectionFactory")
QueueConnectionFactory queueConnectionFactory;
@Asynchronous
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
Future<OrderResult> processOrderAsync(
@Valid OrderRequest request,
@NotNull SecurityContext securityContext) {
try {
// Framework manages transactions, security, validation
Order order = new Order(request);
entityManager.persist(order);
inventoryService.reserveItems(order.getItems());
PaymentResult payment = paymentService.processPayment(order);
sendOrderNotification(order);
auditLogger.logOrderProcessed(order, securityContext.getUserPrincipal());
return new AsyncResult<>(new OrderResult(order, payment));
} catch (Exception e) {
// Container handles rollback
throw new EJBException("Order processing failed", e);
}
}
void sendOrderNotification(Order order) throws JMSException {
try (QueueConnection connection = queueConnectionFactory.createQueueConnection()) {
QueueSession session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
QueueSender sender = session.createSender(orderQueue);
ObjectMessage message = session.createObjectMessage(new OrderNotification(order));
sender.send(message);
}
}
}
// Plus: persistence.xml, ejb-jar.xml, web.xml configuration
// Plus: Application server deployment descriptors
// Plus: JNDI resource configuration
Vanilla DI
Complexity: 0/10class OrderProcessingService {
InventoryService inventoryService;
PaymentService paymentService;
AuditLogger auditLogger;
TransactionManager transactionManager;
EntityManager entityManager;
MessageProducer messageProducer;
ExecutorService executorService;
OrderProcessingService(
InventoryService inventoryService,
PaymentService paymentService,
AuditLogger auditLogger,
TransactionManager transactionManager,
EntityManager entityManager,
MessageProducer messageProducer,
ExecutorService executorService) {
this.inventoryService = inventoryService;
this.paymentService = paymentService;
this.auditLogger = auditLogger;
this.transactionManager = transactionManager;
this.entityManager = entityManager;
this.messageProducer = messageProducer;
this.executorService = executorService;
}
CompletableFuture<OrderResult> processOrderAsync(
OrderRequest request,
User currentUser) {
return CompletableFuture.supplyAsync(() -> {
return transactionManager.executeInTransaction(() -> {
// Validate request manually (or use bean validation)
validateOrderRequest(request);
var order = new Order(request);
entityManager.persist(order);
inventoryService.reserveItems(order.getItems());
var payment = paymentService.processPayment(order);
messageProducer.sendOrderNotification(new OrderNotification(order));
auditLogger.logOrderProcessed(order, currentUser);
return new OrderResult(order, payment);
});
}, executorService);
}
void validateOrderRequest(OrderRequest request) {
if (request == null || request.getItems().isEmpty()) {
throw new IllegalArgumentException("Invalid order request");
}
}
}
// In your application factory:
var orderService = new OrderProcessingService(
new DatabaseInventoryService(dataSource),
new StripePaymentService(stripeApiKey),
new DatabaseAuditLogger(dataSource),
new JpaTransactionManager(entityManager),
entityManager,
new JmsMessageProducer(connectionFactory, orderQueue),
Executors.newFixedThreadPool(20)
);
Experience the jaw-dropping simplicity of creating objects with 'new' and passing them around. No application servers, no JNDI wizardry, no enterprise consultants required!
Support Vanilla DI Development
Maintaining the world's most advanced zero-dependency framework is surprisingly challenging! Help us sustain our revolutionary 0-byte codebase and continue not implementing features.

Your contribution helps us:
- Maintain our cutting-edge negative dependency count
- Fund our research into quantum bytecode subtraction
- Support our elite team of 0 full-time framework architects
- Continue our groundbreaking "anti-documentation" initiative
- Achieve our goal of sub-zero startup times
Fun fact: 100% of donations are immediately invested in not implementing features!
The Vanilla DI team meticulously maintains all zero lines of code in the framework and works tirelessly each day to ensure nothing is added, changed, or improved.
Made with ❤️ for Java developers who’ve grown tired of framework complexity and yearn for the days when new
was enough.
Inspired by the excellent Vanilla JS and powered by constructor injection since 1995