| name | byteman |
| description | Use when debugging, tracing, or mutating state in a running or testable Java application — inject code at method entry/exit, read/write fields, trace calls, throw exceptions, or verify behavior without recompiling |
Byteman — Runtime Java Code Injection
Overview
Byteman is a Java agent that injects code into running applications without recompiling. Use it to:
- Trace method calls, arguments, return values
- Read private fields, internal state
- Mutate state — change field values, force return values, throw exceptions
- Verify behavior — assert methods were called, check ordering
- Debug production-like issues — simulate failures, delays, race conditions
Announce at start: "I'm using the byteman skill to inject diagnostic/mutation code into the Java process."
Setup via JBang
No manual Byteman download needed. JBang has a Byteman catalog that handles everything — agent JAR resolution, classpath, boot classpath — all via --javaagent.
The Key Pattern
jbang --javaagent=byteman@bytemanproject myapp.jar
jbang --javaagent=byteman@bytemanproject org.group:artifact:version
jbang --javaagent=byteman@bytemanproject alias@catalog
jbang --javaagent=byteman@bytemanproject=script:rules.btm myapp.jar
jbang --javaagent=byteman@bytemanproject=script:%{https://url/to/rules.btm} myapp.jar
jbang --javaagent=byteman@bytemanproject=boot:$(jbang info classpath byteman@maxandersen),script:rules.btm myapp.jar
That's it. No BYTEMAN_HOME, no manual JAR downloads, no java -javaagent:... wrangling. JBang resolves Byteman, installs Java if needed, and runs everything.
For Non-JBang Use (manual JAR path)
If you need the raw JAR path for use with plain java or Maven:
BYTEMAN_JAR=$(jbang info classpath org.jboss.byteman:byteman:4.0.25)
Byteman Rule Syntax
Byteman rules are written in .btm files using a simple DSL:
RULE <descriptive name>
CLASS <fully.qualified.ClassName> # or INTERFACE for interface methods
METHOD <methodName>[(parameter types)] # optional parameter types for overloads
AT <location> # WHERE to inject (see locations below)
HELPER <helper class> # optional, defaults to org.jboss.byteman.rule.helper.Helper
BIND <variable bindings> # optional, bind variables for use in conditions/actions
IF <condition> # guard condition (use TRUE for always)
DO <action> # what to do
ENDRULE
Injection Locations (AT clause)
| Location | Description |
|---|
AT ENTRY | Start of method (default) |
AT EXIT | Just before method returns |
AT LINE <n> | At specific source line number |
AFTER READ <field> | After a field is read |
AFTER WRITE <field> | After a field is written |
AT INVOKE <method> | Before a method call within the target |
AFTER INVOKE <method> | After a method call within the target |
AT THROW | When an exception is about to be thrown |
AT EXCEPTION EXIT | When method exits via exception |
Built-in Variables
| Variable | Description |
|---|
$0 | this reference (instance methods) |
$1, $2, ... | Method parameters (by position) |
$! | Return value (only at EXIT) |
$^ | Exception being thrown (only at THROW / EXCEPTION EXIT) |
$CLASS | Name of the class being injected |
$METHOD | Name of the method being injected |
Built-in Helper Actions
| Action | Description |
|---|
traceln(msg) | Print to stdout |
traceStack(msg) | Print message + stack trace |
traceStack(msg, n) | Print message + n stack frames |
flag(id) | Set a named flag |
flagged(id) | Check if flag is set |
countDown(id, n) | Create countdown, returns true when hits 0 |
waiting(id) | Check if a thread is waiting |
waitFor(id) | Block thread until signaled |
signalWake(id) | Wake thread waiting on id |
delay(ms) | Sleep for ms milliseconds |
createTimer(id) | Start a named timer |
getElapsedTimeFromTimer(id) | Read timer value in ms |
return | Force method return (void) |
return <expr> | Force method return with value |
throw new Exception(msg) | Force exception |
callerEquals(name) | Check if caller method matches |
callerMatches(regex) | Check if caller matches regex |
incrementCounter(id) | Increment a named counter |
readCounter(id) | Read counter value |
linked(obj, id) | Check if link exists |
link(obj, id, value) | Attach data to an object |
unlink(obj, id) | Remove linked data |
Common Patterns
1. Trace Method Calls and Arguments
RULE trace service call
CLASS com.example.OrderService
METHOD processOrder
AT ENTRY
IF TRUE
DO traceln(">>> processOrder called with: " + $1 + ", user=" + $2)
ENDRULE
RULE trace return value
CLASS com.example.OrderService
METHOD processOrder
AT EXIT
IF TRUE
DO traceln("<<< processOrder returned: " + $!)
ENDRULE
2. Read Private Fields
RULE dump internal state
CLASS com.example.CacheManager
METHOD get
AT ENTRY
BIND cache = $0.internalMap;
size = cache.size()
IF TRUE
DO traceln("Cache state: size=" + size + ", keys=" + cache.keySet())
ENDRULE
Byteman bypasses access modifiers — $0.privateField just works.
3. Mutate State / Force Return Values
RULE force cache miss
CLASS com.example.CacheManager
METHOD get
AT ENTRY
IF TRUE
DO traceln("Forcing cache miss for key: " + $1);
return null
ENDRULE
RULE inject field value
CLASS com.example.Config
METHOD getMaxRetries
AT ENTRY
IF TRUE
DO return 0
ENDRULE
4. Simulate Failures
RULE simulate database failure
CLASS com.example.DatabasePool
METHOD getConnection
AT ENTRY
IF TRUE
DO throw new java.sql.SQLException("Simulated DB failure")
ENDRULE
RULE simulate slow response
CLASS com.example.HttpClient
METHOD execute
AT ENTRY
IF TRUE
DO delay(5000);
traceln("Injected 5s delay into HTTP call")
ENDRULE
5. Conditional Injection (Nth Call, Specific Args)
RULE fail on third attempt
CLASS com.example.RetryableService
METHOD execute
AT ENTRY
IF countDown("third-call", 3)
DO throw new RuntimeException("Simulated failure on 3rd call")
ENDRULE
RULE trace only admin requests
CLASS com.example.AuthService
METHOD authenticate
AT ENTRY
IF $1.equals("admin")
DO traceln("Admin auth attempt from: " + $2);
traceStack("Admin auth stack: ", 10)
ENDRULE
6. Coordinate Threads (Reproduce Race Conditions)
RULE pause thread A before write
CLASS com.example.AccountService
METHOD debit
AT ENTRY
IF TRUE
DO traceln("Thread " + Thread.currentThread().getName() + " waiting before debit");
waitFor("debit-ready")
ENDRULE
RULE release after thread B reads
CLASS com.example.AccountService
METHOD getBalance
AT EXIT
IF TRUE
DO traceln("Balance read complete, releasing debit");
signalWake("debit-ready")
ENDRULE
7. Count and Verify Calls
RULE count cache hits
CLASS com.example.CacheManager
METHOD get
AT EXIT
IF $! != null
DO incrementCounter("cache-hits")
ENDRULE
RULE report cache stats on shutdown
CLASS com.example.Application
METHOD shutdown
AT ENTRY
IF TRUE
DO traceln("Total cache hits: " + readCounter("cache-hits"))
ENDRULE
How to Run
Option A: JBang (Preferred — Zero Setup)
jbang --javaagent=byteman@bytemanproject=script:rules.btm myapp.jar
jbang --javaagent=byteman@bytemanproject=script:rules.btm org.group:artifact:version
jbang --javaagent=byteman@bytemanproject=script:rules.btm MyApp.java
jbang --javaagent=byteman@bytemanproject=script:rules.btm alias@catalog
jbang --javaagent=byteman@bytemanproject=script:%{https://example.com/rules.btm} myapp.jar
jbang --javaagent=byteman@bytemanproject=boot:$(jbang info classpath byteman@maxandersen),script:rules.btm myapp.jar
jbang --javaagent=byteman@bytemanproject myapp.jar
Option B: Plain Java (When JBang Isn't an Option)
BYTEMAN_JAR=$(jbang info classpath org.jboss.byteman:byteman:4.0.25)
java -javaagent:$BYTEMAN_JAR=script:rules.btm -jar myapp.jar
java -javaagent:$BYTEMAN_JAR=boot:$BYTEMAN_JAR,script:rules.btm -jar myapp.jar
Option C: Attach to Running JVM
BYTEMAN_JAR=$(jbang info classpath org.jboss.byteman:byteman:4.0.25)
jps -l
java -cp $BYTEMAN_JAR org.jboss.byteman.agent.install.Install <PID>
java -cp $BYTEMAN_JAR org.jboss.byteman.agent.submit.Submit rules.btm
java -cp $BYTEMAN_JAR org.jboss.byteman.agent.submit.Submit -l
java -cp $BYTEMAN_JAR org.jboss.byteman.agent.submit.Submit -u rules.btm
Option D: Maven Tests
BYTEMAN_JAR=$(jbang info classpath org.jboss.byteman:byteman:4.0.25)
mvn test -DargLine="-javaagent:$BYTEMAN_JAR=script:rules.btm"
Byteman Rule Validation
Always check rules before loading:
jbang org.jboss.byteman:byteman:4.0.25 -cp "" org.jboss.byteman.check.RuleCheck rules.btm
BYTEMAN_JAR=$(jbang info classpath org.jboss.byteman:byteman:4.0.25)
java -cp $BYTEMAN_JAR org.jboss.byteman.check.RuleCheck rules.btm
java -cp $BYTEMAN_JAR:myapp.jar org.jboss.byteman.check.RuleCheck rules.btm
Workflow for Agent Use
Step 1: Identify the Problem
What are you trying to observe or change?
- Tracing? → AT ENTRY/EXIT + traceln
- State inspection? → BIND fields + traceln
- Failure simulation? → throw / return / delay
- Race condition? → waitFor / signalWake
- Counting? → incrementCounter / readCounter
Step 2: Identify Target Classes and Methods
jar tf myapp.jar | grep -i "ClassName"
javap -p com.example.TargetClass
grep -rn "methodName" src/
Step 3: Write Rules
Create a .btm file with one or more RULE blocks. Name rules descriptively.
Step 4: Validate
BYTEMAN_JAR=$(jbang info classpath org.jboss.byteman:byteman:4.0.25)
java -cp $BYTEMAN_JAR org.jboss.byteman.check.RuleCheck rules.btm
Step 5: Apply
Choose startup attachment or runtime attachment based on context.
Step 6: Observe and Iterate
- Check stdout for traceln output
- Modify rules and re-submit without restart (runtime attach)
- Remove rules when done
Gotchas and Tips
| Issue | Solution |
|---|
| Rule doesn't fire | Check CLASS is fully qualified, METHOD name is exact |
| Can't inject into JDK classes | Use boot:$BYTEMAN_JAR in javaagent args |
| Rule fires too often | Add IF condition to narrow scope |
| Need to match interface method | Use INTERFACE instead of CLASS |
| Multiple overloads | Specify parameter types: METHOD foo(String, int) |
| Inner classes | Use CLASS Outer$Inner |
| Lambda / anonymous classes | Target the enclosing method or use CLASS Outer$1 |
| Constructor injection | Use METHOD <init> |
| Static initializer | Use METHOD <clinit> |
| Want both entry and exit | Write two separate RULE blocks |
| Need to access local vars | Use AT LINE and BIND from stack frame, or restructure |
Quick Reference
jbang --javaagent=byteman@bytemanproject=script:rules.btm myapp.jar
jbang --javaagent=byteman@bytemanproject=script:rules.btm org.group:artifact:version
jbang --javaagent=byteman@bytemanproject=script:rules.btm MyScript.java
jbang --javaagent=byteman@bytemanproject=script:%{https://url/to/rules.btm} myapp.jar
jbang --javaagent=byteman@bytemanproject=boot:$(jbang info classpath byteman@maxandersen),script:rules.btm myapp.jar
BYTEMAN_JAR=$(jbang info classpath org.jboss.byteman:byteman:4.0.25)
java -cp $BYTEMAN_JAR org.jboss.byteman.check.RuleCheck rules.btm
java -javaagent:$BYTEMAN_JAR=script:rules.btm -jar app.jar
java -cp $BYTEMAN_JAR org.jboss.byteman.agent.install.Install $PID
java -cp $BYTEMAN_JAR org.jboss.byteman.agent.submit.Submit rules.btm
java -cp $BYTEMAN_JAR org.jboss.byteman.agent.submit.Submit -l
java -cp $BYTEMAN_JAR org.jboss.byteman.agent.submit.Submit -u rules.btm
mvn test -DargLine="-javaagent:$BYTEMAN_JAR=script:rules.btm"
Red Flags — STOP and Think
- Don't leave mutation rules in production. Byteman is for debugging and testing. Remove rules after investigation.
- Don't use
return without understanding the method contract. Forcing a null return on a method that callers don't null-check will cause NPEs.
- Don't inject delays in time-sensitive systems without warning.
delay() blocks the thread.
- Don't forget to validate rules. Typos in CLASS or METHOD silently fail — the rule just never fires.
- Don't inject into hot paths without understanding overhead. traceln in a tight loop will kill performance.