Troubleshooting & FAQ
Common issues, solutions, and frequently asked questions for Æsh development.
Common Issues
Terminal Issues
Input not working in IDE console
Problem: When running from an IDE (IntelliJ, Eclipse, VS Code), keyboard input doesn’t work correctly.
Solution: IDE consoles often don’t provide a proper TTY. Options:
Run from real terminal: Execute your application from a real terminal/shell instead of the IDE console.
Use the test runner: For development, use
AeshRuntimeRunnerwhich doesn’t need a TTY:
// Development/testing
AeshRuntimeRunner runner = AeshRuntimeRunner.builder()
.command(MyCommand.class)
.build();
String output = runner.execute("mycommand --option value");- Configure IDE: Some IDEs support terminal emulation:
- IntelliJ: Run → Edit Configurations → Check “Emulate terminal in output console”
Colors not displaying
Problem: ANSI colors appear as escape codes instead of colors.
Solution:
- Check terminal support:
Terminal terminal = connection.getTerminal();
if (terminal.getOutputCapability().supportsAnsi()) {
// Safe to use colors
}- Force ANSI on Windows:
// Windows 10+ supports ANSI in Windows Terminal and ConEmu
System.setProperty("org.jline.terminal.dumb.color", "true");- Use theme-aware colors: See Terminal Colors for adaptive colors.
Arrow keys not working
Problem: Pressing arrow keys prints escape sequences instead of moving cursor.
Solution: This usually indicates the terminal is not in raw mode. Ensure you’re using the Connection properly:
// Correct: Use readline's connection handling
readline.readline(connection, "$ ", input -> {
// Arrow keys work here
});
// Wrong: Reading from System.in directly
Scanner scanner = new Scanner(System.in); // Don't do thisTerminal size is wrong
Problem: Application doesn’t fit the terminal or wraps incorrectly.
Solution:
// Get actual terminal size
Size size = connection.size();
int width = size.getWidth();
int height = size.getHeight();
// Handle resize events
connection.setSizeHandler(newSize -> {
// Redraw your UI
redrawInterface(newSize.getWidth(), newSize.getHeight());
});Command Issues
Command not found
Problem: Registered commands aren’t recognized.
Checklist:
- Command is registered:
AeshConsoleRunner.builder()
.command(MyCommand.class) // Is this present?
.start();- Annotation is correct:
@CommandDefinition(name = "mycommand", description = "...") // Check name
public class MyCommand implements Command<CommandInvocation> {- Class implements Command:
public class MyCommand implements Command<CommandInvocation> { // Correct interface?- Check registry:
Set<String> commands = registry.getAllCommandNames();
System.out.println("Registered commands: " + commands);Option values not being set
Problem: Option fields remain null or have default values.
Checklist:
- Annotation is on field:
@Option(shortName = 'v') // Annotation present
private boolean verbose; // Field to set- Option name matches:
# If field is named 'verbose', these work:
mycommand --verbose
mycommand -v # if shortName = 'v'- Has value configuration:
// For boolean flags
@Option(hasValue = false) // Don't expect a value
// For valued options
@Option(hasValue = true) // Expect: --option value- Check option parsing:
@Override
public CommandResult execute(CommandInvocation invocation) {
System.out.println("verbose = " + verbose); // Debug output
return CommandResult.SUCCESS;
}Required option not being enforced
Problem: Command runs even when required option is missing.
Solution: Ensure required = true:
@Option(required = true, description = "Required file path")
private String filePath;If still not working, check that you’re not using overrideRequired = true elsewhere.
History Issues
History not persisting
Problem: Command history is lost between sessions.
Solution: Configure history file:
Settings settings = SettingsBuilder.builder()
.enableHistory(true)
.historyFile(new File(System.getProperty("user.home"), ".myapp_history"))
.historyPersistent(true)
.build();History file not created
Problem: History file doesn’t exist after running.
Checklist:
- Directory exists: The parent directory must exist
- Write permissions: Application needs write access
- Application exits gracefully: History may not save on crash
// Ensure clean shutdown
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// Flush history
}));Completion Issues
Completions not appearing
Problem: Pressing Tab doesn’t show completions.
Checklist:
- Completions are provided:
List<Completion> completions = Arrays.asList(
new Completion("option1"),
new Completion("option2")
);
readline.readline(connection, "$ ", completions, input -> { });- Completer is registered:
@Option(completer = MyCompleter.class)
private String option;- Completer implementation is correct:
public class MyCompleter implements OptionCompleter<CommandInvocation> {
@Override
public void complete(CompleterInvocation invocation) {
String input = invocation.getGivenCompleteValue();
for (String option : options) {
if (option.startsWith(input)) {
invocation.addCompleterValue(option);
}
}
}
}Build Issues
GraalVM native image fails
Problem: Native image compilation fails with reflection errors.
Solution: Add reflection configuration:
// META-INF/native-image/reflect-config.json
[
{
"name": "com.example.MyCommand",
"allDeclaredConstructors": true,
"allDeclaredFields": true,
"allDeclaredMethods": true
}
]See Advanced Topics - GraalVM for complete setup.
ClassNotFoundException at runtime
Problem: Command classes not found at runtime.
Checklist:
- Class is in classpath
- Package name is correct
- For modular Java: Ensure module exports the package
// module-info.java
module myapp {
requires org.aesh;
exports com.example.commands;
}Frequently Asked Questions
General
Q: What’s the difference between Æsh and Æsh Readline?
A:
- Æsh is a high-level framework for building CLI applications with commands, options, and arguments. Use annotations like
@CommandDefinitionand@Option. - Æsh Readline is the low-level library for terminal input handling (line editing, history, completion). Æsh is built on top of Readline.
Choose Æsh for CLI tools. Choose Readline for custom terminal UIs.
Q: Can I use Æsh with Spring Boot?
A: Yes! Create your commands as Spring beans and register them:
@Component
@CommandDefinition(name = "greet", description = "Greet someone")
public class GreetCommand implements Command<CommandInvocation> {
@Autowired
private GreetingService greetingService;
@Override
public CommandResult execute(CommandInvocation invocation) {
invocation.println(greetingService.greet());
return CommandResult.SUCCESS;
}
}
@SpringBootApplication
public class MyApp {
@Autowired
private List<Command<CommandInvocation>> commands;
@PostConstruct
public void startShell() {
AeshConsoleRunner.Builder builder = AeshConsoleRunner.builder();
commands.forEach(builder::command);
builder.start();
}
}Q: How do I create subcommands like git remote add?
A: Use group commands. See Group Commands:
@GroupCommandDefinition(name = "git", description = "Git commands")
public class GitCommand implements GroupCommand<CommandInvocation> {
// ...
}
@CommandDefinition(name = "remote", description = "Remote operations")
public class RemoteCommand implements GroupCommand<CommandInvocation> {
// Add, remove, etc. as subcommands
}Q: Can I run commands programmatically?
A: Yes, use AeshRuntimeRunner:
AeshRuntimeRunner runner = AeshRuntimeRunner.builder()
.command(MyCommand.class)
.build();
String output = runner.execute("mycommand --option value");Options & Arguments
Q: How do I accept multiple values for an option?
A: Use a List or array:
@Option(shortName = 'f')
private List<String> files; // --file a.txt --file b.txtOr with @OptionList:
@OptionList(shortName = 'f')
private List<String> files; // --file a.txt,b.txtQ: How do I make an option accept values without the -- prefix?
A: Use @Argument:
@Argument(description = "Files to process")
private List<File> files; // command file1.txt file2.txtQ: How do I implement password input?
A: Use askIfNotSet with a masked prompt:
@Option(shortName = 'p', askIfNotSet = true,
description = "Password")
private String password;Or manually:
String password = shell.readLine(new Prompt("Password: ", '*'));Q: Can I have options with default values from environment variables?
A: Use defaultValue with environment lookup:
@Option(shortName = 'h',
defaultValue = "${env:DATABASE_HOST:localhost}",
description = "Database host")
private String host;Or in the command:
@Override
public CommandResult execute(CommandInvocation invocation) {
if (host == null) {
host = System.getenv("DATABASE_HOST");
if (host == null) host = "localhost";
}
// ...
}Testing
Q: How do I test commands?
A: Use AeshRuntimeRunner:
@Test
void testCommand() {
AeshRuntimeRunner runner = AeshRuntimeRunner.builder()
.command(MyCommand.class)
.build();
String output = runner.execute("mycommand --name test");
assertTrue(output.contains("Hello, test!"));
}Q: How do I mock dependencies in commands?
A: Use constructor injection or a custom CommandInvocationProvider:
// Constructor injection
public class MyCommand implements Command<CommandInvocation> {
private final MyService service;
public MyCommand(MyService service) {
this.service = service;
}
}
// In test
MyService mockService = mock(MyService.class);
when(mockService.getData()).thenReturn("test data");
AeshRuntimeRunner runner = AeshRuntimeRunner.builder()
.command(new MyCommand(mockService))
.build();Remote Access
Q: How do I add SSH access to my application?
A: Add the SSH dependency and create an SshTerminal. See Remote Connectivity.
Q: Can I use Æsh commands over SSH?
A: Yes! Register commands with AeshConsoleRunner and use it with SSH connection:
SshTerminal ssh = SshTerminal.builder()
.port(2222)
.keyPair(new File("hostkey.ser"))
.connectionHandler(connection -> {
AeshConsoleRunner.builder()
.command(MyCommand.class)
.connection(connection)
.start();
})
.build();Performance
Q: How can I improve startup time?
A:
- Minimize commands at startup: Register only essential commands initially
- Use GraalVM native image: Dramatically reduces startup time
- Lazy-load resources: Don’t load data until needed
Q: How do I handle long-running commands?
A: Don’t block the readline thread:
@Override
public CommandResult execute(CommandInvocation invocation) {
invocation.println("Starting long operation...");
// Run in background
CompletableFuture.runAsync(() -> {
// Long operation
longOperation();
}).thenRun(() -> {
invocation.println("Operation complete!");
});
return CommandResult.SUCCESS;
}Getting Help
If you can’t find a solution here:
- Check the examples: aesh-examples repository
- Search issues: GitHub Issues
- Ask a question: Create a new issue with the “question” label
- Join discussions: GitHub Discussions
When reporting issues, please include:
- Æsh version
- Java version
- Operating system
- Minimal reproducible example
- Expected vs actual behavior