Troubleshooting Guide
This guide helps you resolve common issues when using bytekin.
Transformation Not Applied
Symptoms
- Hook methods are never called
- Original code runs without modifications
- Breakpoints in hooks are never hit
Causes and Solutions
1. Incorrect Class Name
The @ModifyClass
value must exactly match the bytecode class name.
Problem:
@ModifyClass("Calculator") // Wrong!
public class CalcHooks { }
Solution:
@ModifyClass("com.example.Calculator") // Correct
public class CalcHooks { }
How to verify:
# List all classes in JAR
jar tf myapp.jar | grep -i calculator
2. Wrong Method Descriptor
The methodDesc
must exactly match the method signature in bytecode.
Problem:
// Method in bytecode: public int add(int a, int b)
@Inject(methodName = "add", methodDesc = "(int, int)int", at = At.HEAD) // Wrong!
public static CallbackInfo hook() { }
Solution:
@Inject(methodName = "add", methodDesc = "(II)I", at = At.HEAD) // Correct
public static CallbackInfo hook() { }
How to find correct descriptor:
# Use javap to see method signatures
javap -c com.example.Calculator | grep -A 5 "public int add"
3. Hook Class Not Passed to Builder
The hook class must be passed to the Builder.
Problem:
BytekinTransformer transformer = new BytekinTransformer.Builder()
.build(); // Where are the hooks?
Solution:
BytekinTransformer transformer = new BytekinTransformer.Builder(MyHooks.class)
.build(); // Pass hook class
4. Class Not Yet Loaded
Transformations must be applied before the class is loaded by the JVM.
Problem:
// Class already loaded
Class<?> clazz = Class.forName("com.example.MyClass");
// Now trying to transform - too late!
byte[] transformed = transformer.transform("com.example.MyClass", bytecode);
Solution:
- Use a custom
ClassLoader
that applies transformations during loading - Or use Java instrumentation/agents to intercept class loading
Type Mismatch Errors
Symptoms
java.lang.ClassCastException
- Wrong values returned from methods
- Type incompatibility errors
Common Causes
1. Wrong Return Type in CallbackInfo
Problem:
@Inject(methodName = "getCount", methodDesc = "()I", at = At.HEAD)
public static CallbackInfo wrongReturn() {
CallbackInfo ci = new CallbackInfo();
ci.cancelled = true;
ci.returnValue = "42"; // String instead of int!
return ci;
}
Solution:
@Inject(methodName = "getCount", methodDesc = "()I", at = At.HEAD)
public static CallbackInfo correctReturn() {
CallbackInfo ci = new CallbackInfo();
ci.cancelled = true;
ci.returnValue = 42; // Correct: int
return ci;
}
2. Wrong Parameter Types in Hook Method
Problem:
// Target method: void process(int count, String name)
@Inject(methodName = "process", methodDesc = "(ILjava/lang/String;)V", at = At.HEAD)
public static CallbackInfo wrongParams(String name, int count) { // Reversed!
return CallbackInfo.empty();
}
Solution:
@Inject(methodName = "process", methodDesc = "(ILjava/lang/String;)V", at = At.HEAD)
public static CallbackInfo correctParams(int count, String name) { // Correct order
return CallbackInfo.empty();
}
3. Modifying Arguments to Wrong Type
Problem:
@Invoke(..., shift = Shift.BEFORE)
public static CallbackInfo wrongArgType() {
CallbackInfo ci = new CallbackInfo();
ci.modifyArgs = new Object[]{"100"}; // String instead of int
return ci;
}
Solution:
@Invoke(..., shift = Shift.BEFORE)
public static CallbackInfo correctArgType() {
CallbackInfo ci = new CallbackInfo();
ci.modifyArgs = new Object[]{100}; // Correct: int
return ci;
}
Null Pointer Exceptions
Symptoms
- NPE during transformation
- NPE when calling transformed methods
- Stack trace originates from bytecode
Causes and Solutions
1. Returning null from Injection
Problem:
@Inject(methodName = "getValue", methodDesc = "()Ljava/lang/String;", at = At.HEAD)
public static CallbackInfo returnNull() {
CallbackInfo ci = new CallbackInfo();
ci.cancelled = true;
ci.returnValue = null; // Valid for objects, but may not be expected
return ci;
}
Solution:
- Document that null can be returned
- Or return a default value instead:
ci.returnValue = ""; // Empty string instead of null
2. Accessing null Parameters in Hooks
Problem:
@Inject(methodName = "process", methodDesc = "(Ljava/lang/String;)V", at = At.HEAD)
public static CallbackInfo unsafeAccess(String input) {
System.out.println(input.length()); // NPE if input is null!
return CallbackInfo.empty();
}
Solution:
@Inject(methodName = "process", methodDesc = "(Ljava/lang/String;)V", at = At.HEAD)
public static CallbackInfo safeAccess(String input) {
if (input != null) {
System.out.println(input.length());
}
return CallbackInfo.empty();
}
Performance Issues
Symptoms
- Application startup is slow
- Memory usage is high
- Response times are degraded
Causes and Solutions
1. Complex Hook Methods
Problem:
@Inject(methodName = "process", methodDesc = "()V", at = At.HEAD)
public static CallbackInfo slowHook() {
// Database queries
List<Item> items = database.queryAll();
// File I/O
Files.write(Paths.get("log.txt"), data);
// Expensive computations
// ...
return CallbackInfo.empty();
}
Solution:
- Keep hooks simple and fast
- Defer expensive work to background threads
- Use lazy initialization for resources
2. Rebuilding Transformers Repeatedly
Problem:
for (String className : classNames) {
// Creating new transformer for each class!
BytekinTransformer transformer = new BytekinTransformer.Builder(Hooks.class)
.build();
transformer.transform(className, bytecode);
}
Solution:
// Build once, reuse many times
BytekinTransformer transformer = new BytekinTransformer.Builder(Hooks.class)
.build();
for (String className : classNames) {
transformer.transform(className, bytecode);
}
3. Transforming Unnecessary Classes
Problem:
// Applying transformation to all classes, even ones that don't need it
for (String className : allClasses) {
byte[] transformed = transformer.transform(className, bytecode);
}
Solution:
- Transform only specific classes that need it
- Use filtering/naming patterns
- Profile to identify hotspots
Bytecode Verification Errors
Symptoms
java.lang.VerifyError
when loading class- "Illegal type at offset X" errors
- Stack trace is hard to interpret
Common Causes
1. Invalid Bytecode Modifications
This usually means the transformation created invalid bytecode.
How to Debug:
- Use
javap
to inspect the transformed bytecode - Look for unusual instruction sequences
- Verify return types match
2. Incorrect Method Descriptors
An incorrect descriptor can cause verification failures.
Solution:
- Double-check all method descriptors
- Use online descriptor converters to verify
- Compare with
javap
output
Methods Not Found
Symptoms
- Specific methods aren't being transformed
- Overloaded methods cause issues
- Constructor transformations fail
Causes and Solutions
1. Overloaded Methods
Overloaded methods must be distinguished by their full descriptor.
Problem:
// Class has multiple add() methods
// add(int, int) and add(double, double)
@Inject(methodName = "add", methodDesc = "(II)I", at = At.HEAD) // Only matches int version
public static CallbackInfo hook() { }
Solution:
- Use complete descriptor with parameter and return types
- The descriptor automatically distinguishes overloads
2. Private or Internal Methods
Some private methods might not be accessible.
Problem:
@Inject(methodName = "internalMethod", methodDesc = "()V", at = At.HEAD) // Private method
public static CallbackInfo hook() { }
Solution:
- Verify the method is not synthetic or bridge method
- Check that method name and descriptor are exactly correct
Cannot Load Transformed Classes
Symptoms
- ClassNotFoundException after transformation
- Class appears to be missing
- Custom ClassLoader issues
Causes and Solutions
1. Incorrect ClassLoader Setup
Problem:
// Trying to use transformed bytecode with default classloader
byte[] transformed = transformer.transform("com.example.MyClass", bytecode);
Class<?> clazz = Class.forName("com.example.MyClass"); // Won't use transformed bytecode!
Solution:
- Create custom ClassLoader to use transformed bytecode
- Or use instrumentation/agents to intercept loading
2. Bytecode Corruption
The transformation might have produced invalid bytecode.
Solution:
- Verify transformation didn't corrupt bytecode
- Check bytecode size/integrity
- Use bytecode inspection tools
Debugging Tips
1. Enable Verbose Output
// Add debug logging in hooks
@Inject(methodName = "process", methodDesc = "()V", at = At.HEAD)
public static CallbackInfo debug() {
System.out.println("[DEBUG] Hook executed");
System.out.println("[DEBUG] Stack: " + Arrays.toString(Thread.currentThread().getStackTrace()));
return CallbackInfo.empty();
}
2. Inspect Bytecode
# View transformed bytecode
javap -c -private TransformedClass.class
# Look for your injected calls
3. Use a Bytecode Viewer
Tools like Bytecode Viewer or IDEA plugins help visualize bytecode.
4. Profile Performance
# Use JProfiler or YourKit to identify bottlenecks
# Monitor memory usage and CPU time
Common Questions
Q: Can I transform bootstrap classes? A: Not easily with standard classloaders. Use Java agents with instrumentation API.
Q: Do transformations affect serialization? A: Transformed classes will have different bytecode but same serialization format if you don't change fields.
Q: Can I use bytekin in Spring Boot? A: Yes, but you need to configure custom class loading or use agents.
Getting Help
- Check this troubleshooting guide
- Review Best Practices
- Look at Examples
- Open an issue on GitHub
Next Steps
- Review Best Practices
- Check Examples
- Report issues on GitHub