A modern, compile-time dependency injection framework for Java 21+ with event-driven architecture
Status: 0.2.2 on Maven Central. Suitable for early-adopter experimentation. See docs/roadmap.md for what ships today and what's next.
Benchmarked for AI-friendliness → llm-framework-benchmark: on an external-oracle-graded build task (a Kafka → H2 → merged-notification service), run across three models (Claude Sonnet 4.6 / Fable 5 / Opus 4.8, n=5). tiko — absent from the models' training data — reaches 86–100% median compliance, on par with Spring on versions the models know, and clears the brand-new Spring Boot 4.0.6 wall that broke Sonnet 4.6 (median 0%). See the benchmark for the full picture, per-build token cost, and caveats.
A compile-time orchestrator with an integrated event model and full compile-time validation.
Tiko orchestrates, it doesn't bundle — direct access, compile-time safe, nothing wrapped.
The container, the scopes, the event bus, and the wiring graph ship as the framework. HTTP servers, databases, caches, template engines, security libraries, and any other library — you bring directly, and tiko orchestrates the seam via @Produces. Nothing is wrapped or rewritten on the library side; nothing is re-invented on the framework side.
For the longer pitch — design principles, three-layer architecture, event-pipeline trade-offs — see docs/VISION.md.
Every concern in a tiko-built service lands in exactly one bucket. Classify, then write code.
DI container, scopes (SINGLETON / EVENT / PROTOTYPE), event bus, @EventHandler + @EventTrigger, compile-time validation, lifecycle hooks (@PostConstruct, @PreDestroy, ApplicationStartedEvent, ApplicationEndingEvent), @Configuration typed records.
HTTP (Javalin, Jetty, Spark, Vert.x), DataSource (HikariCP, Agroal), schema migrations (Flyway, Liquibase), caching (Caffeine), templates (FreeMarker, Pebble), security (JWT / OAuth via the library of your choice), schedulers, retries, any SDK client. Pattern: declare a factory method annotated @Produces, return the library's value, consume as a constructor parameter. No wrapper, no adapter layer, no annotation-driven dispatch.
Async generalisation, scheduling-as-tick-event, retry-as-event-loop — the small set of unresolved questions about extending the event model itself. An honest scope note, not a deferral promise.
Lookup-table answer to "does tiko support X?":
| Need | Pattern | Recipe in |
|---|---|---|
| HTTP layer | @Produces Javalin (or your choice) + plain route methods |
tiko-build skill, example 15_quickstart |
| Postgres / MySQL | @Produces DataSource returning HikariDataSource |
tiko-build skill, example 15_quickstart |
| Schema migrations | @EventHandler(ApplicationStartedEvent) calling Flyway.migrate() |
tiko-build skill, example 15_quickstart |
| Kafka | @KafkaSource / @KafkaSink — tiko-native module with compile-time validation |
tiko-kafka, example 08_kafka_order_warehouse |
| In-process cache | @Produces Cache<K,V> (Caffeine) |
tiko-build skill |
| Templates | @Produces freemarker.template.Configuration (or Pebble, Mustache) |
tiko-build skill |
| Security | Javalin before handler + Context attributes (open-design caveat in skill §6) |
tiko-build skill §6 |
| Scheduling | @EventHandler on a Tick event published by a small scheduler thread |
tiko-build skill §4.2 |
| Async work | @EventHandler(async = true) for event-shaped work; CompletableFuture / virtual threads for ad-hoc |
tiko-build skill §4.3 |
| Retry | Small utility wrapper — visible code, no AOP | tiko-build skill §4.4 |
| Configuration | Typed @Configuration records |
tiko-config, example 02_config |
| Metrics / tracing | Plug in your library; expose via routes on your HTTP layer | — |
- New service? Read
.ai-skills/tiko-build/SKILL.md— decision tree,@Producescookbook, anti-pattern redirect table so an agent reaches for the tiko-native primitive. - Long-form prose?
docs/orchestrator-model.md. - Reference shape?
tiko-examples/15_quickstart— the canonical small service the skill cites. - Scaffold a fresh project? Maven archetype — see Scaffold a new project below.
- Library not in the cookbook? Read
.ai-skills/tiko-cookbook-extension/SKILL.md— the procedural skill for adding a new recipe. Load-bearing rule: ask, don't fabricate.
// 1. Define your components
@Component(scope = Scope.SINGLETON)
public class UserRepository {
public User findById(String id) { /* ... */ return null; }
}
@Component(scope = Scope.SINGLETON)
public class UserService {
private final UserRepository repository;
@Inject
public UserService(UserRepository repository) {
this.repository = repository;
}
public User getUser(String id) {
return repository.findById(id);
}
}
// 2. Use the container
public class Main {
public static void main(String[] args) {
try (Container container = Tiko.create()) {
UserService service = container.get(UserService.class);
User user = service.getUser("123");
}
}
}The annotation processor validates all dependencies at compile-time and generates the wiring code. Nothing runs by reflection.
Tiko ships to Maven Central. Import the BOM once — then declare the artifacts (and the annotation-processor path) without restating the version:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.tomas-samek</groupId>
<artifactId>tiko-bom</artifactId>
<version>0.2.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.github.tomas-samek</groupId>
<artifactId>tiko-api</artifactId>
</dependency>
<dependency>
<groupId>io.github.tomas-samek</groupId>
<artifactId>tiko-runtime</artifactId>
</dependency>
<!-- Optional, only if you use @Configuration -->
<dependency>
<groupId>io.github.tomas-samek</groupId>
<artifactId>tiko-config</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.github.tomas-samek</groupId>
<artifactId>tiko-processor</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>The tiko-bom import is the single place the version lives; maven-compiler-plugin ≥ 3.12.0 resolves the annotationProcessorPaths version from it too (3.13.0 pinned above for JDK 23+ — see the note below). The BOM also manages the optional tiko-kafka / tiko-mcp / tiko-test modules.
On JDK 23+?
javacno longer runs annotation processing implicitly — the snippet above is already correct (it requiresmaven-compiler-plugin≥ 3.13.0). For Gradle, plainjavac, and the legacy<proc>full</proc>opt-in, see docs/jdk-23-setup.md.
The fastest way to start a fresh project — generates a runnable single-module Tiko DI project with a minimal Main + one @Component, plus AI-assistant context files:
mvn archetype:generate \
-DarchetypeGroupId=io.github.tomas-samek \
-DarchetypeArtifactId=tiko-archetype \
-DarchetypeVersion=0.2.2 \
-DgroupId=com.example \
-DartifactId=my-app \
-DinteractiveMode=false
cd my-app
mvn exec:java # prints: Hello, world!The generated project ships with AI-context files for the major coding agents — CLAUDE.md (canonical), AGENTS.md, .cursor/rules/tiko.md, .github/copilot-instructions.md, .junie/guidelines.md, .ai-skills/SKILL.md, and .ai-skills/tiko-build/SKILL.md (the orchestrator-model skill — decision tree + @Produces cookbook + anti-pattern redirects). Each tool-specific file points at the canonical sources, so edit one file when conventions change.
Every Tiko build emits machine-readable topology + config schema to
META-INF/tiko/. The tiko-mcp companion jar exposes them to any
MCP-aware coding agent (Claude Code, Cursor, …):
java -jar tiko-mcp.jar /path/to/your/project
The metadata ships inside the jar so MCP can also introspect Tiko
dependencies you didn't build yourself. To suppress emission for a
module (closed-source service, sensitive jars), add
-Atiko.topology.bundle=false to the annotation processor args.
See tiko-examples/13_mcp_introspection
for a runnable demo. Projects scaffolded from tiko-archetype ship a
ready-to-use .mcp.json (jbang-resolved) — MCP-aware agents auto-connect
on open with no setup beyond installing jbang.
| Annotation | Purpose | Deep dive |
|---|---|---|
@Component(scope, name, profiles) |
Marks a class for DI | di-and-scopes.md |
@Inject |
Marks the constructor to wire (constructor-only — no field injection) | di-and-scopes.md |
@Named("...") / @Pick(Class) |
Disambiguate when multiple impls exist (string vs class-literal) | di-and-scopes.md |
@Produces(scope, name, profiles) |
Factory method — instance or static | di-and-scopes.md |
@PostConstruct / @PreDestroy |
Lifecycle hooks (AutoCloseable is the recommended cleanup form) |
di-and-scopes.md |
@Configuration(prefix) |
Marks a record as a YAML-backed config root | configuration.md |
@EventHandler(async, eventType) |
Subscribe to events (sync by default, opt-in async) | events.md |
@EventTrigger(eventName, ...) |
Declarative event chains — return-as-payload, guards, spread, async | events.md |
Scopes: SINGLETON > EVENT > PROTOTYPE (longest to shortest lifetime). Shorter-lived beans injected into longer-lived scopes are automatically proxied — see docs/di-and-scopes.md.
Fifteen worked examples ship under tiko-examples/, each a self-contained Maven project:
| # | Module | Demonstrates |
|---|---|---|
| 01 | 01_basic_di |
@Component, scopes, cross-scope proxies, @Produces, @Named, @Pick, Provider<T>, Picker<T>, pick() |
| 02 | 02_config |
@Configuration records, layered ConfigSources, ${VAR} interpolation |
| 03 | 03_events |
Lifecycle events, @EventTrigger chains with guards/spread/async, Event<?> origin tracking |
| 04 | 04_api_impl |
API/impl split — app compiles against an interface jar, impl supplied at runtime |
| 05 | 05_multi_module |
Multi-module aggregation via AggregatingContainer |
| 06 | 06_config_multi_module |
Module-baked META-INF/tiko/defaults.yaml discovery + user override |
| 07 | 07_async_start |
@EventHandler(async = true) on ApplicationStartedEvent — keep slow warmup off the critical path |
| 08 | 08_kafka_order_warehouse |
Cross-JVM Kafka demo — @KafkaSource / @KafkaSink, shared event class, Testcontainers e2e |
| 09 | 09_http_javalin |
TikoJavalin.scoped middleware opens an EVENT scope per route; sync request→response independent of the bus |
| 10 | 10_persistence_jdbc |
Persistence cookbook — EVENT-scoped JDBC transactions across HTTP and batch flows (docs) |
| 11 | 11_custom_logger |
Routing framework logs through slf4j + logback via System.LoggerFinder |
| 12 | 12_testing |
@TikoTest JUnit 5 extension — parameter resolution, RecordingEventBus assertions, scope helpers, awaitAsyncDispatch |
| 13 | 13_mcp_introspection |
Runnable demo of the tiko-mcp topology server reading a built project's META-INF/tiko/ artifacts |
| 14 | 14_profiles |
@Component(profiles = "...") — dev/prod implementation switching with default-profile resolution |
| 15 | 15_quickstart |
Orchestrator-model reference app — HikariCP + Javalin + event-driven handler the tiko-build skill cites |
The comparisons/ directory holds eight self-contained, side-by-side implementations of the same four-singleton, two-module workload — plain Java (no DI), Tiko, Dagger 2, Avaje Inject, HK2, Guice, Spring, and Micronaut (micronaut-inject only). Median of 10 cold JVM invocations, default JVM, default GC, Java 21, on a development laptop. These numbers move on different hardware — re-run locally before drawing conclusions.
| Framework | Wall-clock (ms) | total_ns (ms) |
Style |
|---|---|---|---|
jvm baseline (java -version) |
104 | — | — |
| plain (no DI) | 172 | 36 | floor reference |
| dagger | 186 | 44 | compile-time, lazy |
| tiko | 202 | 61 | compile-time, lazy |
| avaje | 228 | 105 | compile-time, eager |
| hk2 | 307 | 159 | runtime, reflection, lazy |
| guice | 373 | 230 | runtime, reflection, lazy |
| micronaut (inject-only) | 459 | 308 | compile-time, eager + AOP |
| spring | 529 | 368 | runtime, reflection, eager |
The total_ns column sums the four phases the bench measures (create + first_get_a + first_get_b + close) and is the apples-to-apples comparison: it accounts for both eager (Avaje, Spring, Micronaut) and lazy (Tiko, Dagger, Guice, HK2) initialisation strategies. See comparisons/README.md for full per-phase tables, methodology, caveats, and reproduction.
The honest reading: the dominant axis is lazy vs eager init, not "compile-time vs runtime." Four clusters emerge — lean compile-time-lazy (plain, Dagger, Tiko at 36–61 ms total_ns), compile-time-eager (Avaje at 105 ms), runtime-reflection-lazy (HK2, Guice at 159–230 ms), and eager-with-overhead (Micronaut, Spring at 308–368 ms). Within each laziness class the compile-time framework is cheaper (Tiko < Guice; Avaje < Spring), but Avaje (compile-time + eager) is slower than HK2 and Guice (runtime + lazy) — eagerness costs more than reflection saves at this scale.
| Module | Purpose |
|---|---|
tiko-api |
Core annotations and interfaces. The only compile-time dependency your code needs. |
tiko-processor |
Annotation processor — runs at compile-time to validate dependencies and generate wiring code. |
tiko-runtime |
Minimal runtime container. Zero dependencies beyond tiko-api. Ships the in-memory LocalEventBus. |
tiko-config |
YAML-backed configuration injection. The only module that depends on SnakeYAML. Required when your project uses @Configuration; not pulled otherwise. |
tiko-test |
JUnit 5 extension + compile-time @TestComponent overrides + runtime TikoOptions.override(...) + RecordingEventBus spy. Test-scope dependency only. |
tiko-kafka + tiko-kafka-processor |
Kafka transport via the universal TransportBootstrap SPI — @KafkaSource / @KafkaSink, JSON serializer, per-record commit + seek-back. Opt-in. |
tiko-archetype |
Maven archetype that scaffolds a runnable single-module Tiko project in seconds. |
tiko-bom |
Bill-of-materials for version-aligned dependency management. |
Event abstractions (EventBus, EventCallback, Subscription, @EventHandler, @EventTrigger, Event<T>) live in tiko-api; the in-memory implementation lives in tiko-runtime. Kafka ships as a separate, opt-in module pair (tiko-kafka + tiko-kafka-processor); further transports (RabbitMQ, JMS) are planned under Phase 8.
Tiko logs through java.lang.System.Logger — the JDK-standard SPI introduced in
Java 9. There is no tiko-side configuration knob, no SPI to implement, no
adapter module.
Default: routes through java.util.logging. Nothing to configure for "just works."
Routing to slf4j: add slf4j-jdk-platform-logging + your slf4j backend
(logback, log4j-slf4j2-impl, slf4j-simple, etc.). See
tiko-examples/11_custom_logger for a
runnable example.
Routing to log4j2: add log4j-jpl + the log4j2 core. Same mechanism, different bridge.
Routing to JBoss Logging: JBoss Logging uses JulLogManager rather than a
LoggerFinder — set -Djava.util.logging.manager=org.jboss.logmanager.LogManager.
The single tiko-side knob remains TikoOptions.errorHandler(...) for handler-exception
policy — a different layer than framework logging.
| Document | What's in it |
|---|---|
| docs/VISION.md | Long-form pitch, design principles, three-layer architecture, event-pipeline trade-offs. |
| docs/di-and-scopes.md | Full DI reference — scopes, cross-scope proxies, lifecycle hooks, qualifiers, @Produces. |
| docs/configuration.md | @Configuration deep-dive — nested records, layered sources, module-baked defaults. |
| docs/events.md | Event bus, error handling, async executor, lifecycle events, @EventTrigger chains. |
| docs/testing.md | tiko-test JUnit 5 extension — @TikoTest, parameter resolution, RecordingEventBus, scope helpers, known limitations. |
| docs/jdk-23-setup.md | Annotation processing on JDK 23+ — Maven / Gradle / plain javac. |
| docs/roadmap.md | What ships today, what's planned per phase, known limitations. |
| docs/release-process.md | Release engineering notes (maintainers). |
| docs/qa-playbook.md | Structured QA passes over the framework + examples; issue body template; rules for what to file and what not to. |
| docs/issue-fix-playbook.md | Counterpart to the QA playbook — how to work a filed issue without anchoring on its framing. Four-phase fix workflow + common traps. |
| comparisons/README.md | Side-by-side cold-start benchmarks across 8 DI frameworks. |
| docs/superpowers/specs/2026-05-12-kafka-event-bus-design.md | Kafka event bus design — universal transport adapter pattern. |
- Phase 1 — Alpha completion. ✅ Closed. Core DI, scopes, lifecycle events,
@EventTriggerchains, multi-module aggregation. - Phase 2 — Configuration & distributed events. ✅ Closed.
@Configurationv1 + nested records +Set<X>+ YAML source anchors, Kafka transport,ErrorContexthook, async event executor + shutdown timeout,System.Loggermigration. - Phase 3 — Onboarding & tooling. ✅ Closed (30/30 issues, a week ahead of due date). AI-assistant-aware archetype,
tiko-testJUnit 5 module, machine-readable topology + nine-tool MCP server, plus the QA pass #1 follow-ups: examples README cleanup, slf4j routing fix,@PreDestroyLIFO contract honoured across@Componentand@Produces, project-widemaven-failsafe-pluginwiring, dotted-prefix@Configurationnow binds naturally-nested YAML. QA discipline codified in qa-playbook.md + issue-fix-playbook.md. - Phase 4 — Runtime hardening (in progress). Structured
RuntimeExceptionsubtypes, checked-exception propagation through@Produces/@PostConstruct(✅), framework-managed JVM shutdown hook, JaCoCo coverage + SonarCloud static analysis. AOP / metrics / GraalVM dropped from scope until a concrete driver appears. - Phase 5 — Publish to Maven Central. POM metadata, GPG signing, sources/javadoc jars,
central-publishing-maven-plugin+ CI deploy, and the@Namedvs@PickAPI decision. Pulled early so a lean, validated core ships before the heavier feature work. - Phase 6 — MCP enrichment.
get_generated_artifact, a lifecycle-hook query tool, and richer proxy topology — deeper introspection of the compile-time graph. - Phase 7 — Resiliency layer. Timeouts, retries, bounded-queue backpressure, executor pool knobs, DLQ for failed/timed-out events.
- Phase 8 — Distributed transports. RabbitMQ + JMS adapters,
TransportBootstrapSPI audit, pluggable serializer SPI. - Phase 9 — Examples & docs polish. Advanced-feature example gaps (
@EventTriggers, scoped suppliers, origin chain,TikoOptions) plus public-docs tightening.
Full detail in docs/roadmap.md.
git clone https://github.com/tomas-samek/tiko-di.git
cd tiko-di
mvn clean install # build all modules
mvn test # run tests
mvn clean install -DskipTests # build without tests
mvn clean install -pl tiko-api # build specific moduleRequires Java 21+ and Maven 3.8+.
- Compile-time safety. Catch all errors the compiler can see. The only runtime exceptions Tiko throws fire at container startup — never during
container.get(...)in a running application. - Simplicity. Minimal concepts, intuitive API.
- Explicitness. No magic, generated code is readable.
- Performance. Zero reflection, fast startup, low memory.
- Modularity. Use only what you need.
- Event-driven. First-class support for decoupled communication.
Contributions are welcome. Open issues or pull requests on GitHub.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes
- Push to the branch
- Open a Pull Request
Bug reports should include Java version, Maven version, and a minimal reproducer. State symptoms and observable facts only — no "probable cause" or "suggested fix" sections, those bias whoever picks the issue up. The full format and rationale are in docs/qa-playbook.md; the counterpart workflow for working a filed issue is docs/issue-fix-playbook.md.
MIT — see LICENSE.
Built on lessons learned from existing DI frameworks:
- Dagger 2 — compile-time validation approach
- Guice — clean, type-safe API design
- Spring — comprehensive feature set and ecosystem thinking
- Micronaut — cloud-native optimization strategies
Tomas Samek — GitHub
Project: https://github.com/tomas-samek/tiko-di
Tiko — Compile-time dependency injection for Java 21+, with first-class event handling.