Java REPL (jshell) Scripting in Java: Interactive Development and Automation

Introduction

Java REPL (Read-Eval-Print Loop) through jshell, introduced in Java 9, revolutionized how developers interact with Java. It provides an interactive environment for rapid prototyping, testing, and learning Java without the compile-run cycle. This article explores jshell capabilities, integration with applications, and advanced scripting techniques.

What is jshell?

jshell is Java's official REPL tool that allows you to:

  • Execute Java code snippets instantly
  • Test ideas without creating full classes
  • Import and use existing libraries
  • Define methods, classes, and variables on the fly
  • Get immediate feedback with error messages

Getting Started with jshell

Basic Usage

# Start jshell
jshell
# Start with verbose feedback
jshell -v
# Start with pre-loaded imports
jshell --startup DEFAULT --startup PRINTING

Common jshell Commands

// In jshell environment
jshell> /help
jshell> /list
jshell> /vars
jshell> /methods
jshell> /types
jshell> /imports
jshell> /history
jshell> /save filename.java
jshell> /open filename.java
jshell> /reset
jshell> /exit

Programming jshell from Java Applications

Basic jshell Integration

import jdk.jshell.*;
import jdk.jshell.spi.ExecutionControl;
import java.util.List;
import java.util.Locale;
public class BasicJShellRunner {
public static void main(String[] args) {
try (JShell jshell = JShell.create()) {
// Execute simple expressions
List<SnippetEvent> events = jshell.eval("int x = 10;");
for (SnippetEvent event : events) {
System.out.println("Snippet: " + event.snippet().source());
System.out.println("Value: " + event.value());
System.out.println("Status: " + event.status());
}
// Execute another expression using previous variable
events = jshell.eval("x * 2 + 5");
if (!events.isEmpty() && events.get(0).value() != null) {
System.out.println("Result: " + events.get(0).value());
}
}
}
}

Advanced jshell Manager

import jdk.jshell.*;
import jdk.jshell.Snippet.Status;
import java.util.*;
import java.util.stream.Collectors;
public class JShellManager implements AutoCloseable {
private final JShell jshell;
private final List<SnippetEvent> history;
public JShellManager() {
this.jshell = JShell.create();
this.history = new ArrayList<>();
// Set up default imports
setupDefaultImports();
}
private void setupDefaultImports() {
eval("import java.util.*;");
eval("import java.util.stream.*;");
eval("import java.time.*;");
eval("import java.math.*;");
eval("import java.io.*;");
}
public ExecutionResult execute(String code) {
List<SnippetEvent> events = jshell.eval(code);
history.addAll(events);
ExecutionResult result = new ExecutionResult();
result.setEvents(events);
result.setSuccessful(events.stream().allMatch(this::isSuccessful));
return result;
}
private boolean isSuccessful(SnippetEvent event) {
return event.status() == Status.VALID && 
event.exception() == null &&
!event.status().isDefined();
}
public List<VariableSnippet> getVariables() {
return jshell.variables().collect(Collectors.toList());
}
public List<MethodSnippet> getMethods() {
return jshell.methods().collect(Collectors.toList());
}
public List<TypeDeclSnippet> getTypes() {
return jshell.types().collect(Collectors.toList());
}
public void reset() {
jshell.stop();
jshell.snippets().forEach(jshell::drop);
history.clear();
setupDefaultImports();
}
public String getHistory() {
return history.stream()
.map(event -> event.snippet().source())
.collect(Collectors.joining("\n"));
}
@Override
public void close() {
jshell.close();
}
public static class ExecutionResult {
private List<SnippetEvent> events;
private boolean successful;
private String output;
// Getters and setters
public List<SnippetEvent> getEvents() { return events; }
public void setEvents(List<SnippetEvent> events) { this.events = events; }
public boolean isSuccessful() { return successful; }
public void setSuccessful(boolean successful) { this.successful = successful; }
public String getOutput() { return output; }
public void setOutput(String output) { this.output = output; }
}
}

Interactive Java Console Application

import jdk.jshell.*;
import java.util.Scanner;
import java.util.List;
public class InteractiveJavaConsole {
private final JShellManager jshellManager;
private final Scanner scanner;
public InteractiveJavaConsole() {
this.jshellManager = new JShellManager();
this.scanner = new Scanner(System.in);
}
public void start() {
System.out.println("Java REPL Console - Type 'exit' to quit, 'help' for commands");
while (true) {
System.out.print("jshell> ");
String input = scanner.nextLine().trim();
if ("exit".equalsIgnoreCase(input)) {
break;
} else if ("help".equalsIgnoreCase(input)) {
showHelp();
} else if ("reset".equalsIgnoreCase(input)) {
jshellManager.reset();
System.out.println("Session reset.");
} else if ("vars".equalsIgnoreCase(input)) {
showVariables();
} else if ("methods".equalsIgnoreCase(input)) {
showMethods();
} else if ("history".equalsIgnoreCase(input)) {
showHistory();
} else if (!input.isEmpty()) {
executeCode(input);
}
}
jshellManager.close();
System.out.println("Goodbye!");
}
private void showHelp() {
System.out.println("Available commands:");
System.out.println("  exit     - Exit the REPL");
System.out.println("  help     - Show this help");
System.out.println("  reset    - Reset the session");
System.out.println("  vars     - Show defined variables");
System.out.println("  methods  - Show defined methods");
System.out.println("  history  - Show command history");
System.out.println("  [code]   - Execute Java code");
}
private void showVariables() {
List<VariableSnippet> variables = jshellManager.getVariables();
if (variables.isEmpty()) {
System.out.println("No variables defined.");
} else {
variables.forEach(var -> {
System.out.println(var.typeName() + " " + var.name());
});
}
}
private void showMethods() {
List<MethodSnippet> methods = jshellManager.getMethods();
if (methods.isEmpty()) {
System.out.println("No methods defined.");
} else {
methods.forEach(method -> {
System.out.println(method.source());
});
}
}
private void showHistory() {
System.out.println(jshellManager.getHistory());
}
private void executeCode(String code) {
try {
JShellManager.ExecutionResult result = jshellManager.execute(code);
for (SnippetEvent event : result.getEvents()) {
if (event.value() != null) {
System.out.println("==> " + event.value());
}
if (event.exception() != null) {
System.out.println("Error: " + event.exception().getMessage());
}
}
} catch (Exception e) {
System.out.println("Execution error: " + e.getMessage());
}
}
public static void main(String[] args) {
new InteractiveJavaConsole().start();
}
}

Script Execution Engine

import jdk.jshell.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class JavaScriptEngine {
private final JShell jshell;
private final List<ExecutionListener> listeners;
public JavaScriptEngine() {
this.jshell = JShell.builder()
.executionEngine("local")
.build();
this.listeners = new ArrayList<>();
setupEventHandlers();
}
private void setupEventHandlers() {
jshell.onSnippetEvent(event -> {
notifyListeners("SnippetEvent: " + event.snippet().source() + 
" - Status: " + event.status());
});
}
public ScriptExecutionResult executeScript(String script) {
ScriptExecutionResult result = new ScriptExecutionResult();
List<String> outputs = new ArrayList<>();
List<String> errors = new ArrayList<>();
String[] lines = script.split("\n");
for (String line : lines) {
if (line.trim().isEmpty()) continue;
try {
List<SnippetEvent> events = jshell.eval(line);
for (SnippetEvent event : events) {
if (event.value() != null) {
outputs.add(event.value());
}
if (event.exception() != null) {
errors.add("Line: " + line + " - Error: " + 
event.exception().getMessage());
}
}
} catch (Exception e) {
errors.add("Line: " + line + " - Error: " + e.getMessage());
}
}
result.setOutputs(outputs);
result.setErrors(errors);
result.setSuccess(errors.isEmpty());
return result;
}
public ScriptExecutionResult executeScriptFile(Path filePath) throws Exception {
String script = Files.readString(filePath);
return executeScript(script);
}
public void addListener(ExecutionListener listener) {
listeners.add(listener);
}
private void notifyListeners(String message) {
listeners.forEach(listener -> listener.onEvent(message));
}
public void close() {
jshell.close();
}
public static class ScriptExecutionResult {
private List<String> outputs;
private List<String> errors;
private boolean success;
// Getters and setters
public List<String> getOutputs() { return outputs; }
public void setOutputs(List<String> outputs) { this.outputs = outputs; }
public List<String> getErrors() { return errors; }
public void setErrors(List<String> errors) { this.errors = errors; }
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
}
public interface ExecutionListener {
void onEvent(String message);
}
}

Practical Use Cases

1. Data Analysis Scripting

public class DataAnalysisRunner {
public static void main(String[] args) throws Exception {
JavaScriptEngine engine = new JavaScriptEngine();
String analysisScript = """
// Data analysis example
import java.util.*;
import java.util.stream.*;
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
double average = numbers.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0.0);
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Numbers: " + numbers);
System.out.println("Average: " + average);
System.out.println("Sum: " + sum);
System.out.println("Even numbers: " + evenNumbers);
""";
ScriptExecutionResult result = engine.executeScript(analysisScript);
System.out.println("Execution successful: " + result.isSuccess());
result.getOutputs().forEach(System.out::println);
engine.close();
}
}

2. Mathematical Calculations

public class MathCalculationRunner {
public static void runCalculations() {
try (JShell jshell = JShell.create()) {
// Import math utilities
jshell.eval("import java.math.*;");
jshell.eval("import static java.lang.Math.*;");
// Define mathematical functions
jshell.eval("""
double calculateCircleArea(double radius) {
return PI * radius * radius;
}
""");
jshell.eval("""
double calculateCompoundInterest(double principal, double rate, int years) {
return principal * pow(1 + rate/100, years);
}
""");
// Execute calculations
jshell.eval("double area = calculateCircleArea(5.0);");
jshell.eval("double futureValue = calculateCompoundInterest(1000, 5, 10);");
// Print results
jshell.variables().forEach(var -> {
if (var.name().equals("area") || var.name().equals("futureValue")) {
jshell.eval("System.out.println(\"" + var.name() + " = \" + " + var.name() + ");");
}
});
}
}
}

3. API Testing and Prototyping

public class APITestingScript {
public static void testAPI() throws Exception {
JavaScriptEngine engine = new JavaScriptEngine();
String apiTestScript = """
import java.net.http.*;
import java.net.*;
import com.google.gson.*;
// Simple HTTP client setup
HttpClient client = HttpClient.newHttpClient();
// Test GET request
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.coingecko.com/api/v3/ping"))
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Status Code: " + response.statusCode());
System.out.println("Response: " + response.body());
// Parse JSON response
Gson gson = new Gson();
JsonObject jsonResponse = gson.fromJson(response.body(), JsonObject.class);
System.out.println("Parsed JSON: " + jsonResponse);
""";
// Note: This requires Gson in classpath
ScriptExecutionResult result = engine.executeScript(apiTestScript);
if (!result.isSuccess()) {
System.out.println("Errors occurred:");
result.getErrors().forEach(System.err::println);
}
engine.close();
}
}

Advanced jshell Features

Custom Execution Control

import jdk.jshell.execution.*;
import jdk.jshell.spi.ExecutionControl;
import jdk.jshell.spi.ExecutionEnv;
public class CustomJShellEnvironment {
public static JShell createCustomJShell() {
return JShell.builder()
.executionEngine("local", new Object[] {
new DirectExecutionControl() {
@Override
public void load(ExecutionEnv env) {
super.load(env);
// Custom initialization
System.out.println("Custom JShell environment loaded");
}
@Override
public String invoke(String classname, String methodName) 
throws ExecutionControl.UserException {
// Custom method invocation handling
System.out.println("Invoking: " + classname + "." + methodName);
return super.invoke(classname, methodName);
}
}
})
.build();
}
}

Snippet Management

import jdk.jshell.*;
import java.util.*;
public class SnippetManager {
private final JShell jshell;
private final Map<String, Snippet> snippets;
public SnippetManager(JShell jshell) {
this.jshell = jshell;
this.snippets = new HashMap<>();
}
public String executeAndStore(String code, String name) {
List<SnippetEvent> events = jshell.eval(code);
for (SnippetEvent event : events) {
if (event.status() == Status.VALID) {
snippets.put(name, event.snippet());
return event.value();
}
}
return null;
}
public Snippet getSnippet(String name) {
return snippets.get(name);
}
public void updateSnippet(String name, String newCode) {
Snippet oldSnippet = snippets.get(name);
if (oldSnippet != null) {
jshell.drop(oldSnippet);
}
executeAndStore(newCode, name);
}
public List<String> getSnippetNames() {
return new ArrayList<>(snippets.keySet());
}
}

Integration with Build Tools

Maven jshell Plugin

<!-- Add to pom.xml -->
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>com.example.JShellRunner</mainClass>
</configuration>
</plugin>
</plugins>
</build>

Gradle jshell Task

// build.gradle
task jshell(type: JavaExec) {
group = 'Execution'
description = 'Run jshell with project classpath'
classpath = sourceSets.main.runtimeClasspath
mainClass = 'jdk.jshell.JShell'
standardInput = System.in
}
task runScript(type: JavaExec) {
group = 'Execution'
description = 'Run custom jshell script'
classpath = sourceSets.main.runtimeClasspath
mainClass = 'com.example.JShellScriptRunner'
args 'script.jsh'
}

Best Practices

1. Error Handling

public class SafeJShellExecutor {
public static Optional<String> safeEval(JShell jshell, String code) {
try {
List<SnippetEvent> events = jshell.eval(code);
for (SnippetEvent event : events) {
if (event.exception() != null) {
System.err.println("Error: " + event.exception().getMessage());
return Optional.empty();
}
if (event.value() != null) {
return Optional.of(event.value());
}
}
} catch (Exception e) {
System.err.println("Execution failed: " + e.getMessage());
}
return Optional.empty();
}
}

2. Resource Management

public class ResourceAwareJShell implements AutoCloseable {
private final JShell jshell;
private final List<AutoCloseable> resources;
public ResourceAwareJShell() {
this.jshell = JShell.create();
this.resources = new ArrayList<>();
}
public void registerResource(AutoCloseable resource) {
resources.add(resource);
}
@Override
public void close() {
// Close resources in reverse order
Collections.reverse(resources);
for (AutoCloseable resource : resources) {
try {
resource.close();
} catch (Exception e) {
System.err.println("Failed to close resource: " + e.getMessage());
}
}
jshell.close();
}
}

3. Security Considerations

public class SecureJShellEnvironment {
public static JShell createSecureJShell() {
return JShell.builder()
.remoteVMOptions(
"-Djava.security.manager",
"-Djava.security.policy==/path/to/security.policy"
)
.build();
}
}

Conclusion

Java REPL (jshell) provides powerful capabilities for:

  1. Rapid Prototyping - Test ideas without full project setup
  2. Interactive Learning - Learn Java features interactively
  3. Scripting - Write Java scripts for automation
  4. API Testing - Quickly test libraries and APIs
  5. Data Analysis - Perform quick calculations and data manipulation

By integrating jshell into your applications, you can create interactive tools, educational software, and rapid prototyping environments that leverage the full power of Java in an interactive manner.

The key advantages include immediate feedback, reduced boilerplate, and the ability to experiment with Java features in isolation, making it an invaluable tool for both beginners and experienced Java developers.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper