JCT is a Java agent that instruments method calls at runtime and records executed call stacks. It helps answer one practical question in large systems: "Is this code path still used in real traffic?"
- What JCT Does
- Recommended Local Workflow: ELK
- Project Status
- Java Version
- Build
- Configure
- Run an Application with JCT
- Available Processors
- Message Format
- Logging
- Hello World Walkthrough
- Tools
- ELK Integration Guide
- Similar Projects
- License
- Instruments selected classes and methods via
-javaagent - Captures call stacks and timestamps at runtime
- Supports multiple output processors (file and UDP)
- Lets you analyze runtime behavior without changing app code
For local experimentation, the recommended setup is:
-javaagent:jct.jar Docker Compose
┌─────────────────┐ ┌───────────────────────────────────┐
│ JCT Agent │ │ Logstash :9999 │
│ Recorder │────▶│ │ │
│ Stack │ │ ▼ │
│ Processor │ │ Elasticsearch (jct-events-*) │
│ UDP / TCP │ │ │ │
└─────────────────┘ │ ▼ │
│ Kibana :5601 │
└───────────────────────────────────┘
This gives you a fast feedback loop with searchable traces and a UI for exploration.
Start here:
This project targets Java 8 bytecode and is currently focused on practical runtime tracing for legacy and monolithic applications.
Minimum: Java 8
JCT is compiled against Java 8 (-source 8 -target 8) and intentionally uses no APIs beyond that level.
This is a deliberate choice — the primary target is legacy and monolithic systems that are often stuck on older JVMs.
It runs fine on newer JVMs (11, 17, 21, …) without any changes.
mvn clean packageThe distributable agent jar is created at:
target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar
Create a local config file:
mkdir -p "$HOME/.jct"
cp doc/config-sample-file.yaml "$HOME/.jct/config-sample-file.yaml"Notes:
- Default config is loaded from
src/main/resources/META-INF/config.yaml - If
-Djct.config=...is set, custom config is merged with default config
java \
-javaagent:"/path/to/java-code-tracer/target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar" \
-Djct.loglevel=INFO \
-Djct.config="$HOME/.jct/config-sample-file.yaml" \
-Djct.logDir=/tmp/jct \
-noverify \
-jar /path/to/your-application.jarde.marcelsauer.profiler.processor.file.AsyncFileWritingStackProcessor- Example config:
src/test/resources/integration/test-config-asyncfile.yaml
- Example config:
de.marcelsauer.profiler.processor.udp.AsyncUdpStackProcessor- Example config:
src/test/resources/integration/test-config-asyncudp.yaml
- Example config:
de.marcelsauer.profiler.processor.tcp.AsyncTcpStackProcessor- Example config:
src/test/resources/integration/test-config-asynctcp.yaml
- Example config:
{
"stack": [
"de.example.Service.doWork()",
"de.example.Repository.findById()"
],
"timestampMillis": "1528120883697"
}timestampMillis: timestamp in milliseconds when the recorded stack entry startedstack: ordered stack frames from entry to exit point
JCT writes logs to the directory configured with -Djct.logDir.
For more instrumentation details, increase log level:
-Djct.loglevel=DEBUGUse the sample loop jar in doc/helloworld-loop.jar:
java \
-javaagent:"${PWD}/target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar" \
-Djct.loglevel=INFO \
-Djct.config="${PWD}/doc/config-sample-helloworld.yaml" \
-Djct.logDir=/tmp/jct \
-noverify \
-jar "${PWD}/doc/helloworld-loop.jar"Check agent logs:
cat /tmp/jct/jct_agent.logPretty-prints a raw JCT stack array into an aligned, human-readable call sequence.
Consecutive calls to the same class are grouped — package names are abbreviated.
Requires Python 3.9+, no dependencies.
# pipe the bracket string directly
echo '[a.b.Foo.bar(), a.b.Foo.baz()]' | python3 tools/format_stack.py
# from a file
python3 tools/format_stack.py stack.txt
# grab from clipboard (Linux)
xclip -o | python3 tools/format_stack.pyExample output:
# package class method
──────────────────────────────────────────────────────
1 o.b.s.u.ldap LdapConnectionFactory .initialize(LdapConnectionConfigurationDTO)
2 .connect()
3 o.b.s.u.ldap LdapConnectionConfigurationDTO .getLdapServer1()
...
For local Elasticsearch + Logstash + Kibana setup (Docker), UI access, data view setup, and log exploration, see the dedicated guide:
Use the following IntelliJ Run/Debug VM options example when attaching JCT as a Java agent:
GIT_SSH_COMMAND='ssh -i ~/.ssh/niesfisch' git pull
GIT_SSH_COMMAND='ssh -i ~/.ssh/niesfisch' git push