---
title: 'Other JVM Languages'
source: 'https://academia.sh/en/courses/java-ecosystem/other-jvm-languages'
course: 'Build, Testing and the Ecosystem'
language: en
updated: '2026-09-13T05:08:58+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Shows that a class with no source text at all, produced directly in class file format, is loaded and called just like a compiled class as long as it implements an interface'
    - 'Measures that a caller compiled against the same interface sees no behavioral difference between an implementation whose source is Java and one with no source at all, and that its class file references the name of neither'
    - 'Shows that a generated class file can carry a member name with no counterpart in Java syntax, and that this member can only be called through reflection'
---

# Other JVM Languages

It is shown by running that a class produced directly in class file format, with no source text, is loaded and called just like a class compiled with javac, as long as it implements an interface; the caller's class file references the name of neither the real nor the generated implementation. The limiting measurement shows that a generated class file can carry a member name with no counterpart in Java syntax, and that this member can only be called through reflection.

Everything measured across these two lessons lived inside the same runtime: the facade, the
implementation, and the logger were all classes whose source was Java. This lesson removes
that last assumption: does a class file that enters the runtime class path get treated the
same way when its source was never written in Java, and does the caller notice? The question
that carries the whole course is asked here one last time: the runtime class path never
knows which language produced the thing placed on it, only whether it matches the class file
format. This lesson builds that as a run, not a claim: it produces a class file directly in
class file format without going through javac, places it on the runtime class path, and
shows a Java-written caller calling it. No language name, vendor name, or product name
appears — the topic is framed as "another producer of the same class file format."

## Producing a Class File Without Source Text

**LO16.** Writing source text and compiling it is not the only way to produce a class file;
a procedure that directly produces the class file format can also produce one the virtual
machine will accept. **LO17.** A class file produced this way is loaded and called just like
a class compiled from source, as long as it declares an interface and carries the signature
that interface requires.

```java
// Tool.java — shared routines for writing sources, compiling, reading the constant pool and producing sourceless class files (helper class, no main)
import java.io.*;
import java.lang.classfile.*;
import java.lang.classfile.constantpool.*;
import java.lang.constant.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Tool {
    static Path write(Path dir, String name, String source) throws IOException {
        Files.createDirectories(dir);
        Path p = dir.resolve(name + ".java");
        Files.writeString(p, source);
        return p;
    }

    static boolean compile(Path output, List<Path> classPath, List<Path> sources) throws IOException {
        Files.createDirectories(output);
        List<String> args = new ArrayList<>(List.of("-d", output.toString()));
        if (!classPath.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : classPath) sj.add(p.toString());
            args.add("-classpath");
            args.add(sj.toString());
        }
        for (Path s : sources) args.add(s.toString());
        ToolProvider compiler = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter silentOutput = new StringWriter();
        int result = compiler.run(new PrintWriter(silentOutput), new PrintWriter(silentOutput),
                args.toArray(new String[0]));
        return result == 0;
    }

    static void show(String label, Object value) {
        System.out.printf("%-58s: %s%n", label, value);
    }

    static Set<String> references(Path classFile) throws IOException {
        Set<String> names = new TreeSet<>();
        ClassModel model = ClassFile.of().parse(classFile);
        for (PoolEntry e : model.constantPool()) {
            if (e instanceof ClassEntry ce) names.add(ce.asInternalName().replace('/', '.'));
        }
        return names;
    }

    /** Produces a single-method class file, with no source at all, that implements the given interface. */
    static byte[] generate(String className, String interfaceName, String methodName, int constant) {
        ClassDesc self = ClassDesc.of(className);
        ClassDesc iface = ClassDesc.of(interfaceName);
        return ClassFile.of().build(self, cb -> {
            cb.withFlags(ClassFile.ACC_PUBLIC);
            cb.withInterfaceSymbols(iface);
            cb.withMethodBody(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void, ClassFile.ACC_PUBLIC, code -> {
                code.aload(0);
                code.invokespecial(ConstantDescs.CD_Object, ConstantDescs.INIT_NAME, ConstantDescs.MTD_void);
                code.return_();
            });
            cb.withMethodBody(methodName,
                    MethodTypeDesc.of(ConstantDescs.CD_int, ConstantDescs.CD_int),
                    ClassFile.ACC_PUBLIC,
                    code -> {
                        code.iload(1);
                        code.loadConstant(constant);
                        code.iadd();
                        code.ireturn();
                    });
        });
    }
}
```

`Tool.generate` writes no source file at all; it builds the class file's byte sequence
directly — weaving the constant pool, the method table, and the code array by hand. This is
the exact opposite of `Tool.compile`, which calls javac through `ToolProvider` in the
previous two lessons: here the compiler itself is never involved.

```java
// SourcelessGeneration.java — the class file is produced without ever writing source text, but it behaves like a compiled class
import java.net.*;
import java.nio.file.*;
import java.util.*;

public class SourcelessGeneration {
    static String calculatorSource() {
        return "public interface Calculator { int compute(int input); }";
    }

    public static void main(String[] args) throws Exception {
        Path root = Files.createTempDirectory("gl17");
        Path source = root.resolve("source");
        Path interfaceDir = root.resolve("interface");
        Path generatedDir = root.resolve("generated");

        Path sCalculator = Tool.write(source, "Calculator", calculatorSource());
        Tool.compile(interfaceDir, List.of(), List.of(sCalculator));

        Files.createDirectories(generatedDir);
        byte[] bytes = Tool.generate("GeneratedClass", "Calculator", "compute", 1);
        Path classFile = generatedDir.resolve("GeneratedClass.class");
        Files.write(classFile, bytes);

        boolean sourceExists = Files.exists(generatedDir.resolve("GeneratedClass.java"));
        boolean classExists = Files.exists(classFile);

        URL[] url = { interfaceDir.toUri().toURL(), generatedDir.toUri().toURL() };
        Object result;
        try (URLClassLoader y = new URLClassLoader(url, SourcelessGeneration.class.getClassLoader().getParent())) {
            Class<?> iface = Class.forName("Calculator", true, y);
            Class<?> cls = Class.forName("GeneratedClass", true, y);
            Object instance = cls.getDeclaredConstructor().newInstance();
            result = iface.getMethod("compute", int.class).invoke(instance, 10);
        }

        Tool.show("does the GeneratedClass.java source file exist on disk", sourceExists);
        Tool.show("does the GeneratedClass.class class file exist on disk", classExists);
        Tool.show("result of the generated class's compute(10)", result);
    }
}
```

```
does the GeneratedClass.java source file exist on disk    : false
does the GeneratedClass.class class file exist on disk    : true
result of the generated class's compute(10)               : 11
```

`Calculator`'s interface has a source file and was compiled with javac; `GeneratedClass`
has none at all (LO16) — only a `.class` file sits in `generatedDir`. Even so, this class
can be assigned to `Calculator`, and `compute` can be called through the interface, with the
correct result: `10 + 1 = 11` (LO17). What matters to the virtual machine is that the class
file matches the format and carries the required signature; who produced that file, from
which source language, with which tool, never enters into its class-loading decision.

## The Caller Calls Without Noticing a Difference

**LO18.** Only the interface is present on the caller's compile class path; which class will
supply the implementation is a decision the caller does not know at compile time — including
the name of the source language. **LO19.** A caller compiled against the same interface sees
no behavioral difference between an implementation whose source is written in Java and one
with no source at all.

```java
// NoDifferenceSeen.java — an implementation whose source is Java and a generated class with no source at all cannot be told apart by the caller
import java.net.*;
import java.nio.file.*;
import java.util.*;

public class NoDifferenceSeen {
    static String calculatorSource() {
        return "public interface Calculator { int compute(int input); }";
    }

    static String realClassSource() {
        return "public class RealClass implements Calculator {\n"
             + "  public int compute(int input) { return input + 1; }\n"
             + "}";
    }

    static String callerSource() {
        return "public class Caller {\n"
             + "  public static java.util.List<Integer> results = new java.util.ArrayList<>();\n"
             + "  public static void main (String[] a) throws Exception {\n"
             + "    String className = a[0];\n"
             + "    Calculator h = (Calculator) Class.forName(className).getDeclaredConstructor().newInstance();\n"
             + "    for (int input : new int[]{ 1, 5, 100 }) results.add(h.compute(input));\n"
             + "  }\n"
             + "}";
    }

    public static void main(String[] args) throws Exception {
        Path root = Files.createTempDirectory("gl18");
        Path source = root.resolve("source");

        Path sCalculator = Tool.write(source, "Calculator", calculatorSource());
        Path sReal = Tool.write(source, "RealClass", realClassSource());
        Path sCaller = Tool.write(source, "Caller", callerSource());

        Path interfaceDir = root.resolve("interface");
        Path realDir = root.resolve("real");
        Path callerDir = root.resolve("caller");
        Path generatedDir = root.resolve("generated");

        Tool.compile(interfaceDir, List.of(), List.of(sCalculator));
        Tool.compile(realDir, List.of(interfaceDir), List.of(sReal));
        Tool.compile(callerDir, List.of(interfaceDir), List.of(sCaller));

        Files.createDirectories(generatedDir);
        byte[] bytes = Tool.generate("GeneratedClass", "Calculator", "compute", 1);
        Files.write(generatedDir.resolve("GeneratedClass.class"), bytes);

        Object realResult = runAndRead(List.of(callerDir, interfaceDir, realDir), "RealClass");
        Object generatedResult = runAndRead(List.of(callerDir, interfaceDir, generatedDir), "GeneratedClass");

        Tool.show("results for RealClass, whose source is written in Java", realResult);
        Tool.show("results for GeneratedClass, which has no source at all", generatedResult);
        Tool.show("are the two exactly identical", realResult.equals(generatedResult));

        Set<String> refs = Tool.references(callerDir.resolve("Caller.class"));
        Tool.show("does the caller's class file reference Calculator", refs.contains("Calculator"));
        Tool.show("does the caller's class file reference RealClass", refs.contains("RealClass"));
        Tool.show("does the caller's class file reference GeneratedClass", refs.contains("GeneratedClass"));
    }

    static Object runAndRead(List<Path> classPath, String argClassName) throws Exception {
        URL[] url = new URL[classPath.size()];
        for (int i = 0; i < url.length; i++) url[i] = classPath.get(i).toUri().toURL();
        try (URLClassLoader y = new URLClassLoader(url, NoDifferenceSeen.class.getClassLoader().getParent())) {
            Class<?> caller = Class.forName("Caller", true, y);
            caller.getMethod("main", String[].class).invoke(null, (Object) new String[]{ argClassName });
            return caller.getField("results").get(null);
        }
    }
}
```

```
results for RealClass, whose source is written in Java    : [2, 6, 101]
results for GeneratedClass, which has no source at all    : [2, 6, 101]
are the two exactly identical                             : true
does the caller's class file reference Calculator         : true
does the caller's class file reference RealClass          : false
does the caller's class file reference GeneratedClass     : false
```

Neither `RealClass` nor `GeneratedClass` ever appears in `Caller`'s source; which class to
use is decided by the command-line argument `className`, and this comes from outside,
together with the runtime class path (LO18, the same pattern as the facade/implementation
distinction in the first lesson). The result list over three inputs — `[2, 6, 101]` — is
exactly the same in both runs (LO19). Reading the constant pool confirms this: `Caller.class`
references only `Calculator`, neither `RealClass` nor `GeneratedClass`. From the caller's
view there is no difference between the two implementations, because the only thing it sees
is the interface and its promise; whether the source came from a compiler or a byte-sequence
constructor is not part of that promise.

## Limiting Measurement: A Member With No Counterpart in Syntax

**LO20.** The constraint the virtual machine places on a member name is far looser than the
constraint the Java language places on an identifier; almost any string other than a handful
of reserved characters (`.`, `;`, `[`, `/`) is a valid member name, and method names
additionally cannot carry `<` and `>` — these two characters occur only in the special names
`<init>` and `<clinit>`. **LO21.** A method carrying such a name can only be called by its
producer, through reflection; no call line in source text can write this name, because the
compiler's syntax does not accept it as an identifier.

```java
// SyntaxlessMember.java — a class file's member can carry a name that no Java source can ever write
import java.lang.classfile.*;
import java.lang.constant.*;
import java.lang.reflect.*;
import java.net.*;
import java.nio.file.*;

public class SyntaxlessMember {
    public static void main(String[] args) throws Exception {
        Path root = Files.createTempDirectory("gl20");
        Path generatedDir = root.resolve("generated");
        Files.createDirectories(generatedDir);

        String validName = "compute";
        String invalidName = "2#double";

        ClassDesc self = ClassDesc.of("TwoMethodClass");
        byte[] bytes = ClassFile.of().build(self, cb -> {
            cb.withFlags(ClassFile.ACC_PUBLIC);
            cb.withMethodBody(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void, ClassFile.ACC_PUBLIC, code -> {
                code.aload(0);
                code.invokespecial(ConstantDescs.CD_Object, ConstantDescs.INIT_NAME, ConstantDescs.MTD_void);
                code.return_();
            });
            cb.withMethodBody(validName, MethodTypeDesc.of(ConstantDescs.CD_int, ConstantDescs.CD_int),
                    ClassFile.ACC_PUBLIC, code -> {
                        code.iload(1); code.iconst_1(); code.iadd(); code.ireturn();
                    });
            cb.withMethodBody(invalidName, MethodTypeDesc.of(ConstantDescs.CD_int, ConstantDescs.CD_int),
                    ClassFile.ACC_PUBLIC, code -> {
                        code.iload(1); code.loadConstant(2); code.imul(); code.ireturn();
                    });
        });
        Path classFile = generatedDir.resolve("TwoMethodClass.class");
        Files.write(classFile, bytes);

        URL[] url = { generatedDir.toUri().toURL() };
        try (URLClassLoader y = new URLClassLoader(url, SyntaxlessMember.class.getClassLoader().getParent())) {
            Class<?> cls = Class.forName("TwoMethodClass", true, y);
            Object instance = cls.getDeclaredConstructor().newInstance();

            Method valid = cls.getMethod(validName, int.class);
            Method invalid = cls.getMethod(invalidName, int.class);

            boolean validIdentifier = invalidName.length() > 0
                    && Character.isJavaIdentifierStart(invalidName.charAt(0))
                    && invalidName.chars().allMatch(Character::isJavaIdentifierPart);

            Tool.show("call result of the method at the valid name (" + validName + ")", valid.invoke(instance, 10));
            Tool.show("call result of the method at the invalid name (" + invalidName + ")", invalid.invoke(instance, 10));
            Tool.show("\"" + invalidName + "\" is a valid Java identifier", validIdentifier);
        }
    }
}
```

```
call result of the method at the valid name (compute)     : 11
call result of the method at the invalid name (2#double)  : 20
"2#double" is a valid Java identifier                     : false
```

`TwoMethodClass` carries two methods: `compute`, a valid Java identifier, and `2#double`,
which is not — it starts with a digit and carries the `#` character, which an identifier
cannot. The `Character.isJavaIdentifierStart`/`isJavaIdentifierPart` check confirms this:
`false` (LO20). Even so, the virtual machine does not reject the name; the class loads,
`getMethod("2#double", ...)` finds it, `invoke` calls it, and the result is correct:
`10 * 2 = 20`. This is the lesson's limiting measurement: a member entering the runtime class
path only has to follow the class file format's rules, not the source language's syntax. The
distinction is one of format, not semantics: the virtual machine has no idea what
`2#double` means or what contract it fulfills, it only accepts it as a callable member. No
Java source file can ever write this line (LO21); the producing side wrote it directly into
the byte sequence, and the caller found it only through reflection, supplying its name as a
string.

## Summary

- Writing source text and compiling it is not the only way to produce a class file; a
  procedure that directly produces the class file format can also produce a class file the
  virtual machine will load.
- A class produced this way is loaded and called just like a class compiled from source, as
  long as it declares that it implements an interface and carries the signature the
  interface requires.
- Only the interface is present on the caller's compile class path; there is no difference,
  either seen by the caller or referenced by the class file, between an implementation whose
  source is Java and one with no source at all.
- The constraint the virtual machine places on a member name is far looser than the
  constraint the Java language places on an identifier; almost any string other than a
  handful of reserved characters is a valid member name.
- Limiting measurement: a generated class file can carry a member name that no Java source
  could ever write; this member can only be called through reflection, supplying its name as
  a string.

## Course Wrap-Up

The course opened with a single question: a Java program lives against two separate class
paths — the one it compiles against and the one it runs against — and the source text
cannot see either. Twelve lessons asked this question across four mechanisms: the build tool
deriving two paths from a declaration, testing adding a third path, the logging facade
deliberately building this distinction, and this final lesson showing that the runtime class
path never sees the source language at all.

The first claim was confirmed: compiled text does not determine the program that will run —
in this lesson, the caller saw only an interface, and it made no difference whether the class
behind it had passed through a compiler. The second claim — that the defect is mostly not
noisy — was seen again in the logging facade: when no implementation is found, the caller
does not crash, it stays silent. The third claim — that dependency direction is a fact
readable in the compiled product — was tested by reading the constant pool across all three
topics of the course. The boundary was the same from the start: "it compiled" is not proof.

| Lesson | Behavior measured | Path required (compile · runtime · both) | Limiting measurement |
|---|---|---|---|
| Build Tool Model | 3 declared items derive 4 phases; the order is independent of the order they were written in the declaration | both (the same declaration yields two paths at once) | An annotation type kept only in source never appears in the constant pool |
| Dependency Management | Distribution across six types: 1 compile, 3 runtime, 2 both; two of four defects are silent | compile (1) · runtime (3) · both (2) | Not being required on the runtime path is not the absence of a dependency — it can mean the value was inlined |
| Multi-Module Projects | Of three boundary setups, one is caught at compile time, one at runtime, one is never caught | compile · runtime (two uses of the same type) | The boundary is set not by the directory but by the loader's decision; two classes stay in the same directory |
| Packaging and Executable Output | A self-sufficient archive runs on its own; an archive dependent on the outside fails with a runtime error | runtime (the path the archive carries) | The entry point lives in the declaration, not the source; passing compilation does not show the archive is sufficient |
| Unit Test Framework | The production path has one type, the test path has three; with the framework type missing, the same class fails at runtime | both (the test path is a third path) | The order in which the framework calls does not give correctness: the same assertion passes before teardown, then fails after it |
| Mocking | The same compiled test gives two different results through two runtime paths, neither with an error | runtime (where the fake type sits) | When the signature diverges, the defect surfaces at runtime, not at compile time, and the cause is invisible in the test text |
| Parameterized Tests | A single body is called against three data rows; all three pass under an independent setup | runtime (the data declaration) | When the setup is shared, which row passes becomes dependent on data order |
| Integration Tests | The path's content grows from one type to two; the same request is silent in the unit test and throws in the integration test | runtime (the real implementation and its dependency) | Putting the real type back does not validate the test, it only makes another class of defect visible |
| Behavior-Driven Testing | Three of four steps match; the missing step is silent in one setup and halts the run in the other | runtime (the step map) | A full match does not show correctness: a map with its assertion removed still reports a pass with the wrong value |
| Logging Facade and Implementation | The caller's class file references the facade but not the implementation; the implementation enters the runtime class path through a service declaration and is selected by order | both (the facade) · runtime (the implementation) | When no implementation is found at all, the caller compiles and runs without error but produces no records |
| Log Levels and Structured Logging | The number of records the same set of calls produces at two different runtime thresholds; field consistency's effect on readability | runtime (the threshold and handler setting, the field declaration) | A call below the threshold has its argument still evaluated with string concatenation, but not evaluated with a Supplier |
| Other JVM Languages | A class produced with no source text being called just like a compiled class, as long as it implements the interface | runtime (the generated class file) | A generated class file can carry a member name with no counterpart in Java syntax; the caller can only call it through reflection |

Build, Testing and the Ecosystem closes here. The question the course asked — which class
path a given type must be found on — keeps recurring at every step between a Java program's
compilation and its run; the source text never gives the answer to this question on its own.
