Javassist: runtime bytecode editing with a source-level API
Java bytecode engineering toolkit
At a glance
- What is it?
- Javassist lets a Java program define new classes at runtime and rewrite class files as the JVM loads them. The interesting part is the API split: you can write inserted code as Java source text and let the library compile it, or drop to raw bytecode when you need to.
- Who is it for?
- Javassist fits teams that need to add methods, fields or logging to classes at load time and want to express that as Java source rather than raw opcodes: instrumentation agents, frameworks that generate proxies, and test tooling. It is the wrong choice if you need to rewrite a whole method body at the bytecode level with precise control, or if your build already standardises on a bytecode library you understand.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 28 days ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What Javassist actually solves
Javassist is a class library for editing bytecode in Java. The README states two capabilities plainly: it enables a Java program to define a new class at runtime, and it can modify a class file when the JVM loads it. Those are different jobs. The first is generation, where no class file exists yet and you are building one from nothing. The second is transformation, where a compiled class is about to be loaded and you want to change it on the way in.
The audience follows from that. If you are writing a Java agent, a mocking framework, an ORM that needs lazy-loading proxies, or a profiler that wants to time method entry and exit without recompiling the target, you are in scope. If you are writing ordinary application code, you are not. Nothing here helps you structure a service or manage state.
The design choice that separates Javassist from most of its neighbours is stated in the README: it provides two levels of API, source level and bytecode level. With the source-level API, the documentation says users can edit a class file without knowledge of the Java bytecode specification, because the whole API is designed with only the vocabulary of the Java language. That is the pitch. You write the code you want inserted as source text, and Javassist compiles it on the fly.
Two APIs, one class file: how the source-level path works
The mechanism behind the source-level API is a compiler embedded in the library. When you tell Javassist to insert a statement into a method, you hand it a string that looks like Java. The library parses that string, resolves the types it references against the class pool you have configured, emits bytecode, and splices the result into the target method. The README describes this directly: you can specify inserted bytecode in the form of source text, and Javassist compiles it on the fly.
That on-the-fly compilation is where most of the practical friction lives. The inserted text is not compiled by javac, so it does not see your build's full classpath unless you tell Javassist about it. Types you reference have to be reachable through the pool of classes the library knows about. The source-level API also cannot express everything Java can, and the tutorial is the place to check what is supported rather than assuming.
The bytecode-level API is the escape hatch. The README says it allows users to directly edit a class file as other editors do. So the architecture is layered: a convenience layer that trades precision for readability, and a lower layer that gives you the same access as a conventional bytecode editor. Choosing between them is a per-task decision, not a project-wide one. Inserting a timing call around a method is a source-level job. Rewriting a loop's control flow is not.
Adding Javassist to a Maven build and editing a class on load
The repository ships a pom.xml and the README points to javassist.jar as the built artifact containing the class files. The Maven coordinates are not spelled out in the README, so resolve the artifact from your repository of record rather than copying a version string from a blog post. The current release line is Javassist-3.33.0-GA, tagged rel_3_33_0_ga and published on 2026-08-23, with 3.32.0-GA before it on 2026-06-21 and 3.31.0-GA on 2026-04-19.
Once the dependency is on the classpath, the tutorial describes a transformer that receives the class name, the loading class's protection domain and the raw bytes, then hands those bytes to Javassist for editing before the class is defined. The README does not reproduce that snippet, so the only runnable command it does give is the version check below.
java -jar javassist.jarThe README says this prints the version number. That is the quickest way to confirm which build you are actually holding before you wire it into a build, and it is the one command in the README that can be run with no project setup at all.
Where the source-level API stops being convenient
The trade-off in the source-level API is that you give up control over exactly what bytecode comes out. You write a statement; the library decides how to compile it. For simple insertions that is a good deal. For anything involving local variable slots, exception table entries, or stack manipulation, the abstraction leaks and you end up in the bytecode-level API anyway, having paid the cost of learning both.
The compilation step is also a runtime cost and a runtime failure point. A malformed inserted string does not fail your build; it fails when the class is transformed, which may be during application startup or, worse, during a request that triggers lazy loading. There is no compile-time check on the injected source. Teams that adopt Javassist for load-time instrumentation should expect to test the transformation path explicitly rather than trusting that the code compiles because it looks like Java.
A third constraint is class pool management. The pool is the library's view of what classes exist, and it must be populated correctly for the inserted source to resolve. In container environments with multiple class loaders, keeping the pool aligned with the loader you are transforming is work the library does not do for you. The README does not document a rollback path for a transformation that has already been applied to a loaded class, because there is not one at that point: the class is loaded.
Javassist compared with ASM and Byte Buddy
ASM sits at the bytecode level and stays there. You visit a class with a visitor object and emit instructions yourself. There is no source-level layer and no embedded compiler, which means no runtime compilation step and no ambiguity about what bytecode results. The cost is that you must know the instruction set. For a task like stripping every call to a particular method across a jar, ASM's model is a better fit than writing Java source strings that Javassist then compiles.
Byte Buddy takes a third position: it offers a fluent builder API for defining and redefining classes, aimed at the same runtime-generation use cases Javassist addresses. The difference in approach is that Byte Buddy's API is a Java DSL for describing classes, while Javassist's source-level API is a compiler for Java text you supply. If your team is comfortable with a builder DSL, Byte Buddy reads more naturally; if you want to paste a method body in as a string and have it work, Javassist's model is more direct.
The honest summary is that all three edit class files. Javassist's distinguishing feature is that embedded compiler and the two-tier API, not raw capability. If you never intend to use the source-level path, the main reason to pick Javassist over ASM largely disappears.
Licence, releases and the cost of staying current
The README states the software is distributed under the Mozilla Public License Version 1.1, the GNU Lesser General Public License Version 2.1 or later, or the Apache License Version 2.0. That is a tri-licence arrangement, and License.html is the file to read for the terms themselves. This is not legal advice, but the practical implication is that the Apache 2.0 option is the one most corporate distribution policies already have a position on, so the choice of which licence you take the code under is worth confirming with whoever owns that policy rather than assuming.
On maintenance, the repository is not archived and the last push was on 2026-09-02. Releases are frequent: three GA releases landed between 2026-04-19 and 2026-08-23. Changes.md holds the release notes and is the file to read before upgrading, since it is where behavioural changes between GA versions would be recorded.
The upgrade cost is modest for consumers who only use the library as a dependency: bump the version and run your transformation tests. It is higher for anyone relying on the source-level compiler's handling of a specific construct, because that behaviour is the part most likely to shift between releases and the part least likely to be covered by a type checker. Pin the version, keep the transformation tests, and read Changes.md before moving.
Editorial conclusion
Javassist fits teams that need to add methods, fields or logging to classes at load time and want to express that as Java source rather than raw opcodes: instrumentation agents, frameworks that generate proxies, and test tooling. It is the wrong choice if you need to rewrite a whole method body at the bytecode level with precise control, or if your build already standardises on a bytecode library you understand. Before adopting, verify the licence terms against your distribution model, confirm the artifact resolves from Maven Central at the version you pin, and run examples/src locally against your own classpath to see how the source-level compiler handles the code you intend to inject.
Frequently asked questions
What is Javassist used for?
The README describes it as a class library for editing bytecode in Java that lets a program define a new class at runtime and modify a class file when the JVM loads it. In practice that covers runtime class generation and load-time transformation.
What does Javassist do?
It provides two levels of API for bytecode work: a source-level API where you insert Java source text that Javassist compiles on the fly, and a bytecode-level API that lets you edit a class file directly like other bytecode editors.
How does Javassist compare with ASM?
ASM operates at the bytecode level only, with no source-level layer and no embedded compiler. Javassist's README states its source-level API lets users edit a class file without knowledge of the Java bytecode specification, which ASM does not offer.
How does Javassist compare with cglib?
The README does not mention cglib, so no comparison can be drawn from the project's own documentation. What the README does establish is that Javassist works at the class-file level, defining new classes at runtime and modifying classes as they load.
What are the alternatives to Javassist?
The README does not name alternatives, but it does describe the bytecode-level API as working the way other bytecode editors do, which places Javassist in the same category as any library that edits class files directly.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jboss-javassist-javassist)