BTrace is a safe, dynamic tracing framework for the Java platform. It allows you to dynamically instrument running Java applications to inject tracing code without stopping, recompiling, or modifying the application.
Yes, with proper precautions:
- Safe mode (default): BTrace enforces strict safety checks (no threads, no I/O, no loops)
- Sampling: Use
@Sampledto reduce overhead - Level control: Use
@Levelto enable/disable instrumentation dynamically - Test first: Always test scripts in non-production environments first
- Monitor overhead: Watch for performance impact
Not recommended in production:
- Unsafe mode (
-uflag) - Tracing very high-frequency methods
- Scripts without sampling on hot paths
| Aspect | BTrace | Traditional Logging |
|---|---|---|
| Code changes | None required | Requires code modification |
| Deployment | Dynamic attachment | Requires redeployment |
| Overhead when disabled | Zero | Some (log statements still evaluated) |
| Production use | Can add/remove anytime | Fixed at compile time |
| Flexibility | Change what to log without restart | Fixed logging points |
| Learning curve | Steeper | Simpler |
BTrace works with:
- Java 8 through Java 20
- Standard JVMs (HotSpot, OpenJDK)
- Most application frameworks (Spring, Java EE, etc.)
- Containerized applications (Docker, Kubernetes)
Limitations:
- Requires JDK (not JRE) for attach mode
- Cannot instrument native methods
- Some restrictions on boot classpath classes
Impact depends on:
- Number of instrumented methods: More = higher overhead
- Method frequency: Hot paths = significant impact
- Handler complexity: Simple handlers = lower overhead
- Sampling rate: Lower rate = lower overhead
Typical overhead:
- Well-designed scripts: <5% in production
- Aggressive instrumentation: 10-50%
- Poor practices (tracing String.charAt): Can bring system to halt
Best practices to minimize overhead:
// Use sampling
@Sampled(kind = Sampled.Sampler.Adaptive)
// Use level control
@OnMethod(enableAt = @Level(">=1"))
// Aggregate instead of printing each event
@OnTimer(5000)
public static void printSummary() { }Yes. BTrace works well with Spring Boot:
// Trace Spring controller methods
@OnMethod(clazz = "@org.springframework.web.bind.annotation.RestController",
method = "@org.springframework.web.bind.annotation.GetMapping")
// Trace Spring services
@OnMethod(clazz = "@org.springframework.stereotype.Service", method = "/.*/")
// Trace specific beans
@OnMethod(clazz = "com.example.MyService", method = "process")For containerized Spring Boot:
docker exec -it <container> btrace <PID> script.javaSpecify the fully qualified class name:
// Trace JDBC
@OnMethod(clazz = "java.sql.Statement", method = "execute.*")
// Trace HTTP clients
@OnMethod(clazz = "org.apache.http.impl.client.CloseableHttpClient",
method = "execute")
// Trace logging frameworks
@OnMethod(clazz = "org.slf4j.impl.Log4jLoggerAdapter", method = "error")No, BTrace is read-only. You can:
- Observe method calls
- Read arguments and return values
- Track timing and frequency
- Collect statistics
You cannot:
- Modify arguments
- Change return values
- Skip method execution
- Modify application state
For runtime behavior modification, consider:
- Byteman
- AspectJ with load-time weaving
- Java agents with ASM/Byte Buddy
// Script with property
import io.btrace.core.annotations.*;
@BTrace
public class MyTrace {
@Property
public static String filterValue = "default";
@OnMethod(...)
public static void handler(String arg) {
if (arg.equals(filterValue)) {
println("Matched: " + arg);
}
}
}Pass via command line:
btrace <PID> MyTrace.java filterValue=customOr via agent:
java -javaagent:btrace.jar=script=MyTrace.class,filterValue=custom MyAppYes, BTrace works well in microservice architectures:
Per-service tracing:
# Trace specific service
kubectl exec -it pod-name -- btrace <PID> script.javaCommon use cases:
- Trace REST API endpoints
- Monitor service-to-service calls
- Track latency distributions
- Debug intermittent failures
Considerations:
- Each JVM needs separate BTrace attachment
- Use service mesh for distributed tracing (Jaeger, Zipkin)
- BTrace is best for deep debugging, not observability
Use <init> for instance constructors, <clinit> for static initializers:
// Instance constructor
@OnMethod(clazz = "com.example.MyClass", method = "<init>")
public static void onNew() {
println("Object created");
}
// Static initializer
@OnMethod(clazz = "com.example.MyClass", method = "<clinit>")
public static void onStaticInit() {
println("Class initialized");
}Yes, redirect output:
# Redirect to file
btrace <PID> script.java > output.txt
# Or use -o flag
btrace -o output.txt <PID> script.javaFor programmatic file writing (requires unsafe mode):
btrace -u <PID> script.javaimport java.io.*;
@BTrace(unsafe = true)
public class UnsafeFileWriter {
private static FileWriter fw;
@OnMethod(...)
public static void handler() throws Exception {
if (fw == null) {
fw = new FileWriter("/tmp/trace.log");
}
fw.write("Event\n");
fw.flush();
}
}Ideal use cases:
- Production debugging (intermittent issues)
- Performance analysis (method timing, hotspots)
- Understanding third-party library behavior
- Troubleshooting without source code access
- Auditing method calls
- Learning how frameworks work
Not ideal for:
- Permanent monitoring (use APM tools: New Relic, DataDog)
- Distributed tracing (use Zipkin, Jaeger)
- Complex business logic (refactor application)
- Modifying application behavior (use AOP)
- Long-term data collection (use metrics libraries)
Use @Injected without parameters for all services/extensions. The invokedynamic injector
detects how to construct injected services and extensions. If an extension requires runtime
context, it is initialized via Extension.initialize(ExtensionContext).
See also: Architecture → architecture/ExtensionInvokeDynamicBridge.md and
Extension Development Guide → btrace-extension-development-guide.md.
1. Tracing everything
// BAD
@OnMethod(clazz = "/.*/", method = "/.*/")
public static void traceAll() { }2. Expensive operations in handlers
// BAD
@OnMethod(...)
public static void handler() {
for (int i = 0; i < 1000000; i++) { // Expensive loop
// ...
}
}3. No sampling on hot paths
// BAD - called millions of times
@OnMethod(clazz = "com.example.HotClass", method = "hotMethod")
public static void handler() { }
// GOOD
@Sampled(kind = Sampled.Sampler.Adaptive)
@OnMethod(clazz = "com.example.HotClass", method = "hotMethod")
public static void handler() { }4. Printing large objects
// BAD - object might be huge
@OnMethod(...)
public static void handler(Object obj) {
println(str(obj)); // Could be megabytes
}
// GOOD
@OnMethod(...)
public static void handler(Object obj) {
println(name(classOf(obj)) + "@" + str(identityHashCode(obj)));
}5. Forgetting to detach
- BTrace keeps instrumentation active even after client disconnects
- Always properly exit: Ctrl+C → type
exit
Directory structure:
btrace-scripts/
├── common/
│ ├── MethodTiming.java
│ └── ExceptionTracker.java
├── database/
│ ├── SQLLogger.java
│ └── ConnectionPoolMonitor.java
├── http/
│ ├── RequestLogger.java
│ └── ResponseTimeTracker.java
└── memory/
└── AllocationTracker.java
Naming convention:
- Descriptive names:
HttpRequestTrackernotScript1 - Purpose-based:
SlowQueryDetectornotDatabaseTrace - Include target:
SpringControllerTimer
Script template:
/*
* Purpose: [What this script does]
* Target: [Which application/class]
* Usage: btrace <PID> ScriptName.java
* Author: [Your name]
* Date: [Creation date]
*/
import io.btrace.core.annotations.*;
import static io.btrace.core.BTraceUtils.*;
@BTrace
public class ScriptName {
// Script implementation
}| Feature | BTrace | JFR |
|---|---|---|
| Custom instrumentation | Yes | Limited (custom events) |
| Production overhead | Variable (1-50%) | Very low (<1%) |
| Learning curve | Moderate | Low |
| Built into JDK | No | Yes (Java 11+) |
| Dynamic attachment | Yes | Yes |
| Historical data | No | Yes (continuous recording) |
| GUI tools | Limited | Mission Control |
When to use BTrace over JFR:
- Need custom instrumentation points
- Want to trace specific business logic
- Need to instrument third-party libraries
- Want scripting flexibility
When to use JFR over BTrace:
- Need low-overhead continuous monitoring
- Want comprehensive JVM metrics
- Prefer GUI-based analysis
- Need thread dumps and allocation profiling
| Feature | BTrace | AspectJ |
|---|---|---|
| Runtime attachment | Yes | No (requires weaving) |
| Code modification | No | Yes |
| Deployment | Dynamic | Compile-time or load-time |
| Safety checks | Yes (enforced) | No |
| Complexity | Low-Medium | High |
| Use in production | Yes (safe mode) | Yes |
Use BTrace when:
- Need dynamic attachment to running JVM
- Want to avoid modifying application
- Need temporary instrumentation
Use AspectJ when:
- Want to modify behavior (not just observe)
- Need permanent cross-cutting concerns
- Acceptable to weave at compile/load time
| Feature | BTrace | Byteman |
|---|---|---|
| Primary purpose | Tracing/monitoring | Testing/fault injection |
| Safety | Enforced (safe mode) | Optional |
| Script language | Java | Rule-based DSL |
| Behavior modification | No | Yes |
| Learning curve | Medium | Medium-High |
Use BTrace when:
- Production monitoring and debugging
- Want Java-based scripts
- Need enforced safety
Use Byteman when:
- Testing (fault injection)
- Simulating failures
- Need to modify behavior
See Troubleshooting Guide for detailed solutions.
Quick checklist:
- Class/method names correct and fully qualified?
- Method actually being called?
- Imports included?
- Using regex correctly (escaped dots)?
- Tried verbose mode:
btrace -v?
// Add debug output
@OnMethod(...)
public static void handler() {
println("Handler called!");
println("Thread: " + threadName());
println("Stack:");
jstack(3);
}
// Start simple, add complexity
@OnTimer(1000)
public static void heartbeat() {
println("Script alive at " + timestamp());
}Yes, BTrace supports Java 8-20. For Java 9+ you may need to add module opens:
btrace --add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED \
<PID> script.javaOr add to target JVM startup options.
Yes, use @Export to expose metrics via JMX:
@BTrace
public class MetricsExporter {
@Export
private static long requestCount;
@Export
private static long errorCount;
@OnMethod(clazz = "com.example.Service", method = "handleRequest")
public static void onRequest() {
requestCount++;
}
@OnMethod(clazz = "com.example.Service", method = "handleRequest",
location = @Location(Kind.ERROR))
public static void onError() {
errorCount++;
}
}Then access via JMX or integrate with monitoring tools.
BTrace has first-class support for Java Flight Recorder (JFR), allowing you to create custom JFR events with <1% overhead.
Example:
import io.btrace.core.jfr.JfrEvent;
@BTrace
public class JfrIntegration {
@Event(
name = "MethodExecution",
label = "Method Execution Event",
category = {"myapp", "performance"},
fields = {
@Event.Field(type = Event.FieldType.STRING, name = "method"),
@Event.Field(type = Event.FieldType.LONG, name = "duration")
}
)
private static JfrEvent.Factory execEvent;
@TLS private static long startTime;
@OnMethod(clazz = "com.example.Service", method = "process")
public static void onEntry() {
startTime = timeNanos();
}
@OnMethod(clazz = "com.example.Service", method = "process",
location = @Location(Kind.RETURN))
public static void onReturn(@ProbeMethodName String method) {
JfrEvent event = Jfr.prepareEvent(execEvent);
if (Jfr.shouldCommit(event)) {
Jfr.setEventField(event, "method", method);
Jfr.setEventField(event, "duration", timeNanos() - startTime);
Jfr.commit(event);
}
}
}See Getting Started: JFR Integration for complete guide.
Use JFR events when:
- Performance is critical (<1% overhead vs. 1-50% for println)
- You need timeline visualization in JDK Mission Control
- You want offline analysis of captured events
- You're collecting high-frequency data
- You need correlation with other JVM events
Use println when:
- Quick debugging with immediate console output
- Low-frequency events
- Simple ad-hoc tracing
- No need for structured data
Performance comparison:
JFR events: <1% overhead
println: 1-50% overhead (depends on frequency)
Aggregations: ~1-5% overhead
JFR events are significantly faster because:
- Native recording: No string formatting or I/O operations
- Binary format: Events stored in compact binary format
- Async writing: Events written asynchronously by JVM
- Filtering:
shouldCommit()allows JVM-level filtering
Benchmark results (1M events):
- JFR events: ~50ms overhead
- println to console: ~5000ms overhead
- println to file: ~500ms overhead
Yes! BTrace JFR events appear alongside standard JVM events in Mission Control:
Steps:
- Run BTrace script with JFR events
- Start or dump JFR recording:
jcmd <PID> JFR.start name=my-recording jcmd <PID> JFR.dump name=my-recording filename=recording.jfr
- Open
recording.jfrin JDK Mission Control - Navigate to Event Browser → find your custom events
Your BTrace events will have the category you specified (e.g., "myapp") and all defined fields.
Supported:
- OpenJDK 8 with backported JFR ✅
- Java 11+ ✅
Not supported:
- Java 9-10 ❌ (JFR introduced in Java 11, backported to OpenJDK 8)
BTrace gracefully degrades on Java 9-10: JFR event methods return empty events that are silently ignored.
Version detection:
// BTrace automatically handles version differences
// No code changes needed for different Java versionsYes, but with important considerations due to JEP 451 (introduced in JDK 21):
Current behavior (JDK 21+):
- Dynamic agent loading (BTrace attach mode) still works but triggers warnings:
WARNING: A Java agent has been loaded dynamically - The warning advises using
-XX:+EnableDynamicAgentLoadingflag
Solution for JDK 21+:
# Start your application with this flag to suppress warnings
java -XX:+EnableDynamicAgentLoading -jar your-application.jarFuture behavior:
In a future JDK release, dynamic agent loading will be disabled by default. The -XX:+EnableDynamicAgentLoading flag will be required to use BTrace's attach mode.
Alternatives:
- Use agent mode (no attach warnings):
java -javaagent:/path/to/btrace.jar=script=YourScript.class -jar app.jar
- Prepare now: Add
-XX:+EnableDynamicAgentLoadingto your JVM startup scripts for future compatibility
For details, see JEP 451: Prepare to Disallow the Dynamic Loading of Agents and Troubleshooting: JVM Attachment Issues.
Basic pattern:
# Find pod and process
kubectl get pods
kubectl exec <pod-name> -- jps
# Run BTrace
kubectl exec -it <pod-name> -- btrace <PID> script.javaCopy script to pod:
kubectl cp MyTrace.java <pod-name>:/tmp/
kubectl exec -it <pod-name> -- btrace <PID> /tmp/MyTrace.javaTrace multiple pods:
for POD in $(kubectl get pods -l app=myapp -o name | cut -d/ -f2); do
kubectl exec $POD -- btrace 1 script.java &
donePrerequisites:
- JDK (not JRE) must be in container
- BTrace must be available in container image
- Same user permissions as target JVM
shareProcessNamespace: truefor sidecar pattern
See Getting Started: Kubernetes and Troubleshooting: Kubernetes for comprehensive guides.
Yes, using batch scripts or parallel execution:
Shell script approach:
#!/bin/bash
DEPLOYMENT=$1
SCRIPT=$2
PODS=$(kubectl get pods -l app=$DEPLOYMENT -o jsonpath='{.items[*].metadata.name}')
for POD in $PODS; do
echo "Tracing $POD..."
kubectl exec $POD -- btrace 1 $SCRIPT > $POD.log 2>&1 &
done
wait
echo "All traces complete"ConfigMap pattern:
apiVersion: v1
kind: ConfigMap
metadata:
name: btrace-scripts
data:
trace.java: |
@BTrace
public class Trace {
// script content
}Then mount and use across all pods. See Troubleshooting: Batch Tracing.
Sidecar Container (Recommended for persistent tracing):
spec:
shareProcessNamespace: true
containers:
- name: app
image: myapp:latest
- name: btrace
image: bellsoft/liberica-openjdk-debian:11-cds
command: ["/bin/sh", "-c", "sleep infinity"]Pros: Always available, can attach/detach anytime Cons: Extra resource usage Use when: Need on-demand tracing capability
Init Container (For startup tracing):
spec:
initContainers:
- name: setup-btrace
image: btrace:latest
command: ["cp", "-r", "/opt/btrace", "/shared"]
volumeMounts:
- name: shared
mountPath: /sharedPros: No runtime overhead Cons: Only for startup instrumentation Use when: Debugging initialization issues
Agent Mode (For permanent instrumentation):
env:
- name: JAVA_TOOL_OPTIONS
value: "-javaagent:/opt/btrace.jar=script=/scripts/trace.class"Pros: Active from process start Cons: Requires pod restart to change Use when: Need continuous tracing
BTrace doesn't have direct Prometheus integration, but you can use StatSD as a bridge:
Option 1: StatSD → Prometheus
@BTrace
public class MetricsExporter {
@OnMethod(clazz = "com.example.Service", method = "handleRequest")
public static void onRequest() {
// Send to StatSD (configure with -statsd flag)
Statsd.increment("requests.total");
}
@OnMethod(clazz = "com.example.Service", method = "handleRequest",
location = @Location(Kind.ERROR))
public static void onError() {
Statsd.increment("requests.errors");
}
}Run with:
btrace -statsd statsd-exporter:8125 <PID> MetricsExporter.javaThen use statsd_exporter to export to Prometheus.
Option 2: JMX → Prometheus
@BTrace
public class JmxExporter {
@Export
private static long requestCount;
@OnMethod(...)
public static void handler() {
requestCount++;
}
}Use jmx_exporter to scrape JMX metrics.
Recommended: For permanent monitoring, use APM tools (New Relic, DataDog) instead. BTrace is best for ad-hoc debugging.
Yes, BTrace works with service meshes without interference:
Why it works:
- Service mesh operates at network layer (L7)
- BTrace operates at JVM bytecode level
- BTrace uses local sockets (not HTTP)
- No interaction between mesh sidecars and BTrace
Considerations:
-
Resource limits: BTrace overhead may trigger CPU/memory limits
resources: limits: cpu: "500m" # Increase if needed memory: "512Mi"
-
mTLS: Doesn't affect BTrace (uses local communication)
-
Port conflicts: BTrace port 2020 not intercepted by mesh
-
Multi-container pods: Use
shareProcessNamespace: truefor sidecar pattern
Service mesh telemetry and BTrace serve different purposes:
- Service mesh: Service-to-service observability, distributed tracing
- BTrace: Deep JVM-level debugging, method-level insights
Use both together for comprehensive observability.
- Sign the Oracle Contributor Agreement
- Fork the repository
- Create a feature branch
- Submit a pull request
See Contributing Guide for details.
- Sample scripts:
btrace-dist/src/main/resources/samples/(50+ examples) - Tutorial: BTrace Tutorial
- Wiki: BTrace Wiki
- GitHub: BTrace Issues (search for examples)
- Documentation Hub - Complete documentation map and learning paths
- Getting Started Guide
- Quick Reference
- Troubleshooting Guide
- BTrace Tutorial
- BTrace Wiki
- Slack: btrace.slack.com
- Gitter: gitter.im/btraceio/btrace
- GitHub: github.com/btraceio/btrace
- BTrace Maven Plugin: github.com/btraceio/btrace-maven
- VisualVM BTrace Plugin: visualvm.github.io
BTrace is licensed under GPLv2 with the Classpath Exception. See LICENSE for details.