Maciej Walkowiak

Activate Maven Profile by Operating System

Russia has invaded Ukraine and already killed tens of thousands of civilians, with many more raped and tortured. Ukraine needs your help!

Help Ukraine Now!

Who would have thought that you can develop a command line application with Spring? Since Spring Boot 3 has a support for building native executables, it has become not only possible but also a reasonable choice! Let's dive into how to do it step by step.

Create a new Spring Boot project

  • use GraalVM distrubution of Java
  • select "Picocli" in the dependency list
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootPicocliSampleApplication {

    public static void main(String[] args) {
        System.exit(SpringApplication.exit(SpringApplication.run(SpringBootPicocliSampleApplication.class, args)));
    }
}
import java.util.concurrent.Callable;

import picocli.CommandLine.Command;

import org.springframework.stereotype.Component;

@Component
@Command
public class MainCommand implements Callable<Integer> {
    @Override 
    public Integer call() {
        System.out.println("Hello from the main command");
        return 0;
    }
}
import picocli.CommandLine;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.stereotype.Component;

@Component
public class PicoCommandLineRunner implements CommandLineRunner, ExitCodeGenerator {

    private final MainCommand mainCommand;

    private final CommandLine.IFactory factory;

    private int exitCode;

    public PicoCommandLineRunner(MainCommand mainCommand, CommandLine.IFactory factory) {
        this.mainCommand = mainCommand;
        this.factory = factory;
    }

    @Override
    public void run(String... args) {
        exitCode = new CommandLine(mainCommand, factory).execute(args);
    }

    @Override
    public int getExitCode() {
        return exitCode;
    }
}

Run with:

$ ./mvnw package
$ java -jar target/spring-boot-picocli-sample-0.0.1-SNAPSHOT.jar

Build to native:

...

Add options:

@Component
@Command
public class MainCommand implements Callable<Integer> {

    @CommandLine.Option(names = "--name", required = true)
    private String name;

    @Override public Integer call() {
        System.out.println("Hello %s from the main command".formatted(name));
        return 0;
    }
}

Add parameters:

@CommandLine.Parameters(index = "0", defaultValue = "0")
private int times;

@Option and @Parameters annotation can be placed either on field or on a setter method. in native image it must be on the field.

Add help command

@Command(subcommands = CommandLine.HelpCommand.class)
public class MainCommand implements Callable<Integer> {
    // ...
}

or

@Command(mixinStandardHelpOptions = true) // adds help and version

get rid of the default logging

logging.level.root=error

create custom banner

Ascii Banner Generator

and paste it into src/main/resources/banner.txt

you can use ${AnsiColor.BLUE} etc

remember to set to default at the end

${AnsiColor.DEFAULT}.

remove the banner

spring.main.banner-mode=off

Add version command

normally version could be read from the manifest but it's not possible with current status of spring native

  1. Add property to proeprties in pom.xml
<app.version>${project.version}</app.version>
  1. Generate version.properties file during the build:
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>properties-maven-plugin</artifactId>
    <version>1.1.0</version>
    <executions>
        <execution>
            <phase>generate-resources</phase>
            <goals>
                <goal>write-project-properties</goal>
            </goals>
            <configuration>
                <outputFile>${project.build.outputDirectory}/version.properties</outputFile>
            </configuration>
        </execution>
    </executions>
</plugin>