Live (re)compile, (re)load, (re)execute Java code in 100 LoC

Writing your first program in Java can have quite a learning curve.
Java has been trying in recent releases to make it easier to write the first simple program.
Here are some changes that simplify your first Java program:

JEP 222: jshell: The Java Shell (Read-Eval-Print Loop) in JDK 9

JEP 330: Launch Single-File Source-Code Programs in JDK 11

JEP 445: Unnamed Classes and Instance Main Methods in preview in JDK 21

JEP 458: Launch Multi-File Source-Code Programs. Not released yet.

In this article, we’ll write a program (The Reloader) to easily play with Java code without the need to know how to compile or run the code or print the result.

The code will be automatically (re)compiled, (re)loaded, (re)executed and (re)printed to the output when the .java file is saved.

On the left side, the Java file that I edited and saved. On the right side, the code re-executed.

JShellJava 11+Java 21 (–enable-preview)ReloaderExecute java files(.jsh)No class neededNo complex main methodNo System.out.println neededRe-execute on file changed

What we’ll need:

Source code to play with

Code to detect when the source code has been modified

A compiler to recompile the code when the file has changed

A way to reload the generated class file after the compilation

A method to call when the new code is reloaded

The source code

import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class PlayWithNumbers implements Supplier<String> {
@Override
public String get() {
IntStream numbers = new Random().ints(20, 0, 100);
return numbers.sorted().mapToObj(Integer::toString).collect(Collectors.joining(“, “));
}
}

The Reloader

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
* Live Reload and execution of another java file.
* The other Java file needs to implements the Supplier interface.
* Public domain code.
*
* @author Anthony Goubard – japplis.com
*/
public class Reloader {
private String fileName;

public Reloader(String fileName) {
this.fileName = fileName;
try {
monitorForReload();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}

private void monitorForReload() throws IOException {
Path file = Path.of(fileName);
if (Files.notExists(file)) throw new FileNotFoundException(file + ” not found”);
if (!file.toString().endsWith(“.java”)) throw new IllegalArgumentException(file + ” is not a .java file”);
Runnable compileAndRun = () -> { // statement executed each time the file is saved
try {
boolean compiled = compile(file);
if (compiled) execute(file);
} catch (Exception ex) {
System.err.println(ex.getClass().getSimpleName() + “: ” + ex.getMessage());
}
};
compileAndRun.run(); // First execution at launch
monitorFile(file, compileAndRun);
}

private boolean compile(Path file) throws IOException, InterruptedException {
String javaHome = System.getenv(“JAVA_HOME”);
String javaCompiler = javaHome + File.separator + “bin” + File.separator + “javac”;
Process compilation = Runtime.getRuntime().exec(new String[] {javaCompiler, file.toString()});
compilation.getErrorStream().transferTo(System.err); // Only show compilation errors
compilation.waitFor(1, TimeUnit.MINUTES); // Wait for max 1 minute for the compilation
boolean compiled = compilation.exitValue() == 0;
if (!compiled) System.err.println(“Compilation failed”);
return compiled;
}

private void execute(Path file) throws Exception {
// Execution is done in a separate class loader that doesn’t use the current class loader as parent
URL classpath = file.toAbsolutePath().getParent().toUri().toURL();
URLClassLoader loader = new URLClassLoader(new URL[] { classpath }, ClassLoader.getPlatformClassLoader());
String supplierClassName = file.toString().substring(file.toString().lastIndexOf(File.separator) + 1, file.toString().lastIndexOf(“.”));
// Create a new instance of the supplier with the class loader and call the get method
Class<?> stringSupplierClass = Class.forName(supplierClassName, true, loader);
Object stringSupplier = stringSupplierClass.getDeclaredConstructor().newInstance();
if (stringSupplier instanceof Supplier) {
Object newResult = ((Supplier) stringSupplier).get();
System.out.println(“> ” + newResult);
}
}

public static void monitorFile(Path file, Runnable onFileChanged) throws IOException {
// Using WatchService was more code and less reliable
Runnable monitor = () -> {
try {
long lastModified = Files.getLastModifiedTime(file).to(TimeUnit.SECONDS);
while (Files.exists(file)) {
long newLastModified = Files.getLastModifiedTime(file).to(TimeUnit.SECONDS);
if (lastModified != newLastModified) {
lastModified = newLastModified;
onFileChanged.run();
}
Thread.sleep(1_000);
}
} catch (Exception ex) {
ex.printStackTrace();
}
};
Thread monitorThread = new Thread(monitor, file.toString());
monitorThread.start();
}

public static void main(String[] args) {
if (args.length == 0) {
System.err.println(“Missing Java file to execute argument.”);
System.exit(-1);
}
new Reloader(args[0]);
}
}

Now start it with java Reloader.java PlayWithNumbers.java and edit the PlayWithNumbers.java file as you wish.

Playing with numbers and stream.

Going further

In this example, we created a minimal version (100 lines of code) that could easily be extended with the following features:

Provide multiple java files in the arguments

Provide a directory instead of a Java file

Detect if there is a pom.xml, gradle.properties or build.xml and build with Apache Maven, Gradle or Apache Ant instead of javac

Provide external libraries to the URLClassLoader

Detect the return type of the Supplier (String, List, Array, JComponent, …) and show the result accordingly

Open a JShell file (.jsh), create a Java file from it and execute it

Use JBang so that installing and running Java get even easier

Some of these ideas, I’ve implemented in my Applet Runner IDE plug-in where I recently added support for local java files for URLs.

The post Live (re)compile, (re)load, (re)execute Java code in 100 LoC appeared first on foojay.