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:
- Rapid Prototyping - Test ideas without full project setup
- Interactive Learning - Learn Java features interactively
- Scripting - Write Java scripts for automation
- API Testing - Quickly test libraries and APIs
- 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.