Flow is a lightweight Java agent that captures end-to-end distributed execution traces of Java applications running on servlet containers (for example, Tomcat).
It is designed primarily for performance analysts and engineers to understand where time is spent inside and across services, not for generic logging or metrics collection.
Flow instruments applications at runtime using Byte Buddy and produces hierarchical, distributed traces that show:
- Request entry points
- Method execution paths
- Cross-service HTTP calls
- Time spent at each node and layer
Modern enterprise applications are:
- Highly concurrent
- Distributed across multiple JVMs and nodes
- Composed of many internal and external service calls
Traditional logs and metrics cannot answer questions like:
- Where exactly did this request spend time?
- Which downstream service caused the slowdown?
- How did this request flow across nodes?
Flow answers these questions by capturing distributed traces that follow a request across JVMs and services.
- HTTP request tracing for servlet-based applications
- Method-level spans with parent and child relationships
- Distributed tracing across services
- W3C Trace Context (traceparent) propagation
- Works across nodes and JVMs
- Automatically creates client spans
- Injects trace context into outbound HTTP requests
- Bytecode instrumentation only
- No blocking I/O on the request path
- Selectively trace specific classes or patterns
- No recompilation required for configuration changes
┌────────────┐
│ Browser │
└─────┬──────┘
│
▼
┌──────────────┐ traceparent ┌──────────────┐
│ Service A │ ────────────────────────▶ │ Service B │
│ (Node A) │ │ (Node B) │
│ │ │ │
│ SERVER span │ │ SERVER span │
│ CLIENT span │ │ │
└──────────────┘ └──────────────┘
Each request produces a distributed trace graph spanning:
- Multiple methods
- Multiple threads
- Multiple JVMs
- Agent intercepts the servlet filter chain
- Extracts or creates trace context
- Starts a root SERVER span
- Selected application methods are instrumented
- Nested spans are created automatically
- Apache HttpClient is instrumented
- CLIENT spans are created
- Trace context is injected via the traceparent header
- Extract trace context
- Continue the same distributed trace
mvn clean packageThis produces:
target/flow-agent-<version>.jar
Add the agent to your JVM startup:
-javaagent:/path/to/flow-agent.jarExample (Tomcat):
export JAVA_OPTS="-javaagent:/opt/flow/flow-agent.jar"
catalina.sh runInstrumentation is controlled via a properties file packaged in the agent:
src/main/resources/flow-agent.properties
Example:
trace.classes=com.example.employee.servlet.EmployeeServlet,
com.example.employee.service.EmployeeService,
com.example.employee.dao.EmployeeDao
trace.regex=.*Service.*,.*Dao.*This allows adding or removing traced classes without rebuilding the agent.
The agent produces hierarchical trace data that can be:
- Logged (development)
- Exported to a collector (future)
- Visualized as call trees, flame graphs, or service flow diagrams
POST /employees 11.36 ms
└─ LoginFilter.doFilter() 11.26 ms
└─ EmployeeServlet.service() 11.12 ms
└─ EmployeeService.updateEmployee() 10.61 ms
└─ EmployeeDao.updateEmployee() 8.36 ms
- ✅ HTTP inbound tracing (Tomcat)
- ✅ Method-level spans
- ✅ Distributed tracing across services
- ✅ Apache HttpClient support
- 🚧 Trace exporting and visualization (in progress)
- 🚧 Async execution propagation (planned)
- 🚧 Sampling and scale optimizations (planned)
- Correct distributed traces first
- Minimal runtime overhead
- Clear execution flow for performance analysts
- Extensible architecture for future backends
- Full APM replacement
- Log aggregation
- Metrics-only monitoring
Flow is focused on execution flow and performance analysis, not general observability.
MIT License (or your preferred license)
Contributions, design discussions, and performance insights are welcome. Please open an issue or submit a pull request.