Good question! Part of code are generated using `MethodHandle` such as some field getters, or constructor. But the overall code are generated as source code first at runtime instead of bytecode. This is because binary protocol are very complicated. Generated the bytecode directly will make the troubleshooting more difficult. but it's possible to generated the bytecode techniquely. We have an IR abstraction, it's possible to change it to generate the bytecode
The danger with generating source code is that you now depend on source rules which may have changed, never applied to your source language, or are made more complicated by class loading refs. I’d definitely go for byte code generation, and possibly using constant dynamic to handle any really tricky constants, over source code any day.
I would go for `MethodHandle`s and lambda metafactory over bytecode where possible. Much easier to avoid and debug bugs in the generation (better errors than those provided by Hotspot's verifier), and avoids needing to worry about constant dynamic at all. Of course, once you need to implement more than one method, you can no longer just use lambda metafactory.
A while back I actually implemented some utilities for generating classes at runtime and "linking" constants into them using constant dynamic. One day I might clean it up and release it as a library.
I'm not sure whether `MethodHandle` can generate the most complicated code, since the serialization logic here are more complicated even than the manual written code.
Janino can generated the bytecode for fury generated java code.
I must agree that generating bytecode directly has it's advantages, the abstraction is more low-level, thus more flexible, except more complicated for developing.