A cheat sheet for experienced users. For step-by-step instructions, see Getting Started. Back to README.
| Method | Command |
|---|---|
| JBang | jbang catalog add --name btraceio https://raw.githubusercontent.com/btraceio/jbang-catalog/main/jbang-catalog.json && jbang btrace@btraceio <PID> script.java |
| SDKMan | sdk install btrace |
| Docker | docker pull btrace/btrace |
| Manual | Download latest release |
- Core Annotations
- Location Kinds
- Parameter Annotations
- Other Annotations
- Common Patterns
- CLI Commands
- Built-in Functions
Marks a class as a BTrace script.
@BTrace
public class MyTrace { }Primary annotation for method instrumentation.
| Parameter | Type | Description | Example |
|---|---|---|---|
clazz |
String | Target class(es) | "com.example.MyClass" |
method |
String | Target method(s) | "processData" |
type |
String | Method signature filter | "void (java.lang.String)" |
location |
Location | Where to inject code | @Location(Kind.RETURN) |
enableAt |
Level | Instrumentation level control | @Level(">=1") |
exactTypeMatch |
boolean | Exact type matching | true |
Examples:
// Match specific class and method
@OnMethod(clazz = "com.example.MyClass", method = "myMethod")
// Match with regex
@OnMethod(clazz = "/com\\.example\\..*/", method = "/get.*/")
// Match by annotation
@OnMethod(clazz = "@javax.ws.rs.Path", method = "@javax.ws.rs.GET")
// Match by signature
@OnMethod(clazz = "com.example.MyClass", method = "calculate", type = "int (int, int)")Execute periodically.
@OnTimer(5000) // Every 5 seconds
public static void periodic() { }Handle custom events sent from BTrace console.
@OnEvent("myevent")
public static void onMyEvent() { }Execute when traced process exits.
@OnExit
public static void onExit(int exitCode) { }Execute when memory is low.
@OnLowMemory(pool = "Tenured Gen")
public static void onLowMemory() { }Define a JFR (Java Flight Recorder) event factory for high-performance event recording.
Requirements: OpenJDK 8 (with backported JFR) or Java 11+
@Event(
name = "MyEvent",
label = "My Custom Event",
description = "Description of the event",
category = {"myapp", "performance"},
stacktrace = true,
fields = {
@Event.Field(type = Event.FieldType.STRING, name = "message"),
@Event.Field(type = Event.FieldType.LONG, name = "duration",
kind = @Event.Field.Kind(name = Event.FieldKind.TIMESPAN))
}
)
private static JfrEvent.Factory myEventFactory;Field Types: BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE, BOOLEAN, STRING, CLASS, THREAD
Field Kinds: TIMESTAMP, TIMESPAN, DATAAMOUNT, FREQUENCY, MEMORYADDRESS, PERCENTAGE, BOOLEANFLAG, UNSIGNED
See Getting Started: JFR Integration and Pattern #9 below.
Define a handler for periodic JFR events (OpenJDK 8 or Java 11+).
@PeriodicEvent(
name = "PeriodicStats",
label = "Periodic Statistics",
period = "10 s", // or "eachChunk", "beginChunk", "endChunk"
fields = @Event.Field(type = Event.FieldType.LONG, name = "count")
)
public static void emitStats(JfrEvent event) {
if (Jfr.shouldCommit(event)) {
Jfr.setEventField(event, "count", getCount());
Jfr.commit(event);
}
}Specify where to inject code within a method using @Location(Kind.XXX).
| Kind | Description | When | Common Parameters |
|---|---|---|---|
ENTRY |
Method entry | Default | Method args |
RETURN |
Method return | Normal exit | @Return, @Duration |
ERROR |
Exception thrown | Uncaught exception | Throwable, @Duration |
CALL |
Method call | Before/after call | @TargetInstance, @TargetMethodOrField |
LINE |
Source line | Specific line | Line number |
FIELD_GET |
Field read | Getting field value | @TargetInstance, @TargetMethodOrField |
FIELD_SET |
Field write | Setting field value | New value, @TargetInstance |
NEW |
Object creation | After new |
Object type name |
NEWARRAY |
Array creation | After new[] |
Array type, dimensions |
CATCH |
Exception catch | Entering catch block | Throwable |
THROW |
Throwing exception | Before throw | Throwable |
ARRAY_GET |
Array read | Getting array element | Array, index |
ARRAY_SET |
Array write | Setting array element | Array, index, value |
SYNC_ENTRY |
Enter synchronized | Acquiring lock | Lock object |
SYNC_EXIT |
Exit synchronized | Releasing lock | Lock object |
INSTANCEOF |
instanceof check | Type check | Type name, @TargetInstance |
CHECKCAST |
Type cast | Casting | Type name, @TargetInstance |
Location Modifiers:
@Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/") // Any call
@Location(value = Kind.CALL, clazz = "java.io.File", method = "delete") // Specific call
@Location(value = Kind.LINE, line = 42) // Specific line numberInject context information into probe handler parameters.
| Annotation | Type | Description | Available In |
|---|---|---|---|
@Self |
Object | Current instance (this) |
All |
@Return |
Method return type | Return value | RETURN, CALL (after) |
@Duration |
long | Duration in nanoseconds | RETURN, ERROR, CALL (after) |
@ProbeClassName |
String | Enclosing class name | All |
@ProbeMethodName |
String | Enclosing method name | All |
@TargetInstance |
Object | Target object | CALL, FIELD_GET/SET |
@TargetMethodOrField |
String | Target name | CALL, FIELD_GET/SET |
Examples:
// Method entry with arguments and context
@OnMethod(clazz = "MyClass", method = "process")
public static void onEntry(@Self Object self,
@ProbeClassName String clazz,
@ProbeMethodName String method,
String arg1, int arg2) { }
// Method return with value and duration
@OnMethod(clazz = "MyClass", method = "calculate", location = @Location(Kind.RETURN))
public static void onReturn(@Return int result, @Duration long duration) { }
// Method call tracking
@OnMethod(clazz = "MyClass", method = "/.*/", location = @Location(Kind.CALL, clazz = "/.*/", method = "/.*/"))
public static void onCall(@TargetInstance Object target, @TargetMethodOrField String method) { }Injects an extension/service instance into your script.
- Use plain
@Injected(no parameters). The invokedynamic injector auto-detects whether the service needs a runtime context or a no-arg construction and wires it accordingly. - Annotation type:
org.openjdk.btrace.core.annotations.Injected - Example:
import org.openjdk.btrace.core.annotations.*;
import org.openjdk.btrace.metrics.MetricsService;
@BTrace
public class LatencyProbe {
@Injected
private static MetricsService metrics;
}See also: Architecture → architecture/ExtensionInvokeDynamicBridge.md.
Control sampling rate.
@Sampled(kind = Sampled.Sampler.Const) // Constant sampling
@Sampled(kind = Sampled.Sampler.Adaptive) // Adaptive sampling
@OnMethod(...)
public static void sampledHandler() { }Control handler activation by instrumentation level.
@OnMethod(clazz = "...", enableAt = @Level(">=1")) // Level 1 or higher
@OnMethod(clazz = "...", enableAt = @Level("2..5")) // Level 2-5 range
@OnMethod(clazz = "...", enableAt = @Level("3")) // Exactly level 3Export data via JMX.
@Export
private static long counter;Declare script properties.
@Property
public static String configValue = "default";Per-thread state.
@TLS
private static long threadStartTime;@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() {
long duration = timeNanos() - startTime;
println("Duration: " + str(duration / 1000000) + " ms");
}private static Map<String, AtomicInteger> counts = Collections.newHashMap();
@OnMethod(clazz = "com.example.Service", method = "/.*/")
public static void onMethod(@ProbeMethodName String method) {
AtomicInteger counter = Collections.get(counts, method);
if (counter == null) {
counter = Atomic.newAtomicInteger(0);
Collections.put(counts, method, counter);
}
Atomic.incrementAndGet(counter);
}
@OnTimer(5000)
public static void printStats() {
printMap(counts);
}@OnMethod(clazz = "com.example.Service", method = "/.*/", location = @Location(Kind.ERROR))
public static void onError(@ProbeClassName String clazz,
@ProbeMethodName String method,
Throwable t) {
println("Exception in " + clazz + "." + method + ": " + str(t));
jstack();
}@OnMethod(clazz = "com.example.State", method = "/.*/", location = @Location(Kind.FIELD_SET, clazz = "/.*/", field = "status"))
public static void onFieldSet(@TargetInstance Object target,
@TargetMethodOrField String field,
Object newValue) {
println("Field " + field + " set to: " + str(newValue));
}@OnMethod(clazz = "java.sql.Statement", method = "execute.*")
public static void onSqlExecute(@Self Object stmt, String sql) {
println("SQL: " + sql);
}@OnMethod(clazz = "+javax.servlet.http.HttpServlet", method = "service")
public static void onRequest(@Self Object servlet,
javax.servlet.http.HttpServletRequest req) {
println(str(req.getMethod()) + " " + str(req.getRequestURI()));
}private static long objectCount;
@OnMethod(clazz = "com.example.HeavyObject", method = "<init>")
public static void onNew() {
objectCount++;
}
@OnTimer(5000)
public static void printCount() {
println("Objects created: " + str(objectCount));
}private static Aggregation distrib = Aggregations.newAggregation(AggregationFunction.QUANTIZE);
@OnMethod(clazz = "com.example.Service", method = "process", location = @Location(Kind.RETURN))
public static void onReturn(@Duration long duration) {
Aggregations.addToAggregation(distrib, duration / 1000000); // Convert to ms
}
@OnTimer(10000)
public static void printDistribution() {
Aggregations.printAggregation("Duration distribution (ms)", distrib);
}import org.openjdk.btrace.core.jfr.JfrEvent;
@BTrace
public class JfrMethodTrace {
@Event(
name = "MethodExecution",
label = "Method Execution Event",
category = {"myapp"},
fields = {
@Event.Field(type = Event.FieldType.STRING, name = "className"),
@Event.Field(type = Event.FieldType.STRING, name = "methodName"),
@Event.Field(type = Event.FieldType.LONG, name = "duration",
kind = @Event.Field.Kind(name = Event.FieldKind.TIMESPAN))
}
)
private static JfrEvent.Factory execEventFactory;
@TLS private static long startTime;
@OnMethod(clazz = "com.example.Service", method = "/.*/")
public static void onEntry() {
startTime = timeNanos();
}
@OnMethod(clazz = "com.example.Service", method = "/.*/",
location = @Location(Kind.RETURN))
public static void onReturn(@ProbeClassName String clazz,
@ProbeMethodName String method) {
JfrEvent event = Jfr.prepareEvent(execEventFactory);
if (Jfr.shouldCommit(event)) {
Jfr.setEventField(event, "className", clazz);
Jfr.setEventField(event, "methodName", method);
Jfr.setEventField(event, "duration", timeNanos() - startTime);
Jfr.commit(event);
}
}
}Benefits: <1% overhead, native JVM recording, offline analysis with Mission Control.
Attach to running JVM and trace.
btrace [options] <PID> <script.java> [script-args]Common Options:
--version- Show the version-v- Run in verbose mode-l- List all locally attachable JVMs-lp- List active probes in the given JVM (expects PID or app name)-r <probe-id>- Reconnect to an active disconnected probe-r help- Show help on remote commands-o <file>- Output to file (disables console output)-u- Run in trusted/unsafe mode-d <path>- Dump instrumented classes to specified path-pd <path>- Search path for probe XML descriptors-cp <path>/-classpath <path>- User class files and annotation processors path-I <path>- Include files path-p <port>- Port for btrace agent listener (default: 2020)-host <host>- Remote host (default: localhost)-statsd <host:port>- StatSD server configuration-x- Unattended mode (non-interactive)
Examples:
# List all Java processes
btrace -l
# Basic attach
btrace 12345 MyTrace.java
# Verbose with output file
btrace -v -o trace.log 12345 MyTrace.java
# List active probes in a JVM
btrace -lp 12345
# Reconnect to an active probe
btrace -r myprobe-id 12345
# With script arguments
btrace 12345 MyTrace.java arg1 arg2
# Dump instrumented classes for debugging
btrace -d /tmp/instrumented 12345 MyTrace.java
# With StatSD integration
btrace -statsd localhost:8125 12345 MyTrace.javaCompile BTrace script.
btracec <script.java>Examples:
# Compile script
btracec MyTrace.java
# Results in MyTrace.classLaunch Java app with BTrace agent.
btracer <script.class> <java-app-and-args>Examples:
# First compile
btracec MyTrace.java
# Then launch
btracer MyTrace.class java -jar myapp.jar
# With JVM options
btracer MyTrace.class java -Xmx2g -jar myapp.jarStart app with BTrace agent directly.
java -javaagent:/path/to/btrace-agent.jar=script=<script.class>[,arg=value]... YourAppAgent Parameters:
script=<path>- BTrace script class filescriptdir=<dir>- Directory to load scripts fromport=<port>- Communication portnoServer=true- Don't start command serverbootClassPath=<path>- Additional boot classpath
Examples:
# Basic agent mode
java -javaagent:btrace-agent.jar=script=MyTrace.class MyApp
# With custom port
java -javaagent:btrace-agent.jar=script=MyTrace.class,port=2020 MyAppAll functions from org.openjdk.btrace.core.BTraceUtils.
println(String) // Print line
print(String) // Print without newline
printArray(Object[]) // Print array
printMap(Map) // Print map
printf(String, ...) // Formatted printstr(Object) // Object to string
name(Class) // Class name
probeClass() // Get current probe class
classOf(Object) // Get object's class
field(Class, String) // Get field valuenewAggregation(AggregationFunction) // Create aggregation
addToAggregation(Aggregation, long) // Add value
printAggregation(String, Aggregation) // Print distributiontimeMillis() // Current time in milliseconds
timeNanos() // Current time in nanoseconds
timestamp() // Formatted timestampjstack() // Print stack trace
jstack(int) // Print N frames
jstacks() // Print all thread stacks
threadId() // Current thread ID
threadName() // Current thread nameCollections.newHashMap() // Create HashMap
Collections.newArrayList() // Create ArrayList
Collections.put(Map, K, V) // Put in map
Collections.get(Map, K) // Get from map
Collections.size(Collection) // Get sizeAtomic.newAtomicInteger(int) // Create AtomicInteger
Atomic.incrementAndGet(AtomicInteger) // Increment and get
Atomic.get(AtomicInteger) // Get valueexit(int) // Exit BTrace
sys(String) // System property
getenv(String) // Environment variable
getInstrumentationLevel() // Current levelsizeof(Object) // Object size
heapUsage() // Heap memory usage
nonHeapUsage() // Non-heap memory usage// Create event from factory
Jfr.prepareEvent(JfrEvent.Factory) // Create new JFR event instance
// Set event field values (overloaded for all types)
Jfr.setEventField(JfrEvent, String, byte|boolean|char|short|int|float|long|double|String)
// Event lifecycle
Jfr.shouldCommit(JfrEvent) // Check if event should be recorded
Jfr.commit(JfrEvent) // Commit event to JFR
Jfr.begin(JfrEvent) // Start timing for timespan events
Jfr.end(JfrEvent) // End timing for timespan eventsNote: JFR functions require OpenJDK 8 (with backported JFR) or Java 11+. Not available in Java 9-10 (graceful degradation).
BTrace scripts have safety restrictions:
- No new threads
- No new classes
- No synchronization
- No loops (for, while, do-while, enhanced for)
- No external method calls (methods within same BTrace class are allowed)
- No file I/O (except via BTraceUtils)
- No System.exit
Use -u (unsafe mode) to bypass restrictions, but only in controlled environments.
- Documentation Hub - Complete documentation map and learning paths
- Getting Started Guide - Installation, first script, and quick start
- BTrace Tutorial - Progressive lessons covering all features
- Troubleshooting Guide - Solutions to common problems
- FAQ - Common questions and best practices