# Load External Commands
Arthas can load external command jars at startup. This is useful when you want to package team-specific diagnostic actions as Arthas commands. After an external command is loaded, you can use it like a built-in command and inspect it with help.
TIP
External commands are loaded only when the Arthas server starts. If Arthas has already attached to the target JVM, copying a jar into the command directory will not take effect until the Arthas server is restarted.
# Loading methods
# Command line
arthas-boot.jar supports the --command-locations option. Each entry can be a jar file path or a directory path, separated by commas:
java -jar arthas-boot.jar --command-locations '/opt/arthas/ext-command.jar,/opt/arthas/ext-commands' <pid>
If you use as.sh from the full distribution, you can pass the same option:
./as.sh --command-locations '/opt/arthas/ext-command.jar,/opt/arthas/ext-commands' <pid>
# arthas.properties
Configure arthas.commandLocations in arthas.properties:
arthas.commandLocations=/opt/arthas/ext-command.jar,/opt/arthas/ext-commands
If you start Arthas through arthas-spring-boot-starter, use arthas.command-locations in Spring Boot configuration files:
arthas.command-locations=/opt/arthas/ext-command.jar,/opt/arthas/ext-commands
# Default commands directory
If ${arthas.home}/commands exists, Arthas also loads *.jar from that directory at startup:
mkdir -p ${arthas.home}/commands
cp arthas-demo-external-command.jar ${arthas.home}/commands/
Explicit arthas.commandLocations are loaded first, followed by the default ${arthas.home}/commands directory.
# Directory scanning rules
- Each location can point to a jar file or a directory.
- Directory entries scan only
*.jarfiles in the current directory. Subdirectories are not scanned recursively. - The same jar path is deduplicated by its canonical absolute path.
- If an external command depends on third-party jars, package the dependencies into the command jar, or put those dependency jars in a scanned directory.
# Writing an external command
External command jars expose CommandResolver implementations through Java SPI. A minimal command usually has three parts:
- A command class that extends
AnnotatedCommand. - A
CommandResolverimplementation that returns the command list. - A
META-INF/services/com.taobao.arthas.core.shell.command.CommandResolverfile that contains the resolver class name.
Example command:
package demo.command;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.middleware.cli.annotations.Argument;
import com.taobao.middleware.cli.annotations.Description;
import com.taobao.middleware.cli.annotations.Name;
import com.taobao.middleware.cli.annotations.Summary;
@Name("demo-external")
@Summary("Demo external command loaded from arthas.home/commands")
@Description("Examples:\n"
+ " demo-external\n"
+ " demo-external Codex\n")
public class DemoExternalCommand extends AnnotatedCommand {
private String message;
@Argument(index = 0, argName = "message", required = false)
@Description("message printed by the demo external command")
public void setMessage(String message) {
this.message = message;
}
@Override
public void process(CommandProcess process) {
String value = message;
if (value == null || value.trim().isEmpty()) {
value = "hello";
}
process.write("demo external command loaded: " + value + "\n");
process.end();
}
}
Example CommandResolver:
package demo.command;
import java.util.Collections;
import java.util.List;
import com.taobao.arthas.core.shell.command.Command;
import com.taobao.arthas.core.shell.command.CommandResolver;
public class DemoExternalCommandResolver implements CommandResolver {
@Override
public List<Command> commands() {
return Collections.singletonList(Command.create(DemoExternalCommand.class));
}
}
SPI file path:
src/main/resources/META-INF/services/com.taobao.arthas.core.shell.command.CommandResolver
SPI file content:
demo.command.DemoExternalCommandResolver
# Maven dependencies
External commands depend on Arthas command interfaces and CLI annotations. These dependencies should usually use provided scope to avoid packaging Arthas classes into the external command jar:
<dependencies>
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-core</artifactId>
<version>${arthas.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.middleware</groupId>
<artifactId>cli</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
The Arthas repository provides a complete example: arthas-demo-external-commandopen in new window.
# Build and verify
Use the demo module as an example:
./mvnw -pl arthas-demo-external-command -DskipTests package
Copy the generated jar to ${arthas.home}/commands/ and start Arthas:
mkdir -p ${arthas.home}/commands
cp arthas-demo-external-command/target/arthas-demo-external-command-*.jar ${arthas.home}/commands/
java -jar arthas-boot.jar <pid>
Run the command in Arthas:
help demo-external
demo-external Codex
Expected output:
demo external command loaded: Codex
# Loading behavior and conflicts
At startup, Arthas appends external command jars to the Arthas ClassLoader and discovers commands with ServiceLoader<CommandResolver>. Command registration follows these rules:
- External commands cannot override built-in commands. If an external command has the same name as a built-in command, it is skipped and a log is written.
- If multiple external commands have the same name, only the first command is kept. Later duplicates are skipped and logged.
- A broken
CommandResolverdoes not stop other resolvers from loading. The error is written to the Arthas log. - If no valid external command is found, Arthas continues to start normally.