+ * This annotation is registered with NullAway as an {@code ExternalInitAnnotation},
+ * suppressing "field not initialized" errors for the annotated class without requiring
+ * per-class {@code @SuppressWarnings("NullAway.Init")}.
+ *
+ * @author Tim Sparg
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface PicocliManaged {
+
+}
diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/PicocliRunner.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/PicocliRunner.java
new file mode 100644
index 00000000..88343625
--- /dev/null
+++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/PicocliRunner.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.javaformat.cli;
+
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.ExitCodeGenerator;
+import org.springframework.stereotype.Component;
+import picocli.CommandLine;
+import picocli.CommandLine.IFactory;
+
+/**
+ * Runs the picocli command line.
+ *
+ * @author Tim Sparg
+ */
+@Component
+class PicocliRunner implements CommandLineRunner, ExitCodeGenerator {
+
+ private final SpringJavaFormatCommand command;
+
+ private final IFactory factory;
+
+ private int exitCode;
+
+ PicocliRunner(SpringJavaFormatCommand command, IFactory factory) {
+ this.command = command;
+ this.factory = factory;
+ }
+
+ @Override
+ public void run(String... args) {
+ this.exitCode = new CommandLine(this.command, this.factory).execute(args);
+ }
+
+ @Override
+ public int getExitCode() {
+ return this.exitCode;
+ }
+
+}
diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/SpringJavaFormatCliApplication.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/SpringJavaFormatCliApplication.java
new file mode 100644
index 00000000..ee8e3294
--- /dev/null
+++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/SpringJavaFormatCliApplication.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.javaformat.cli;
+
+import org.springframework.boot.Banner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Main application for the Spring JavaFormat CLI.
+ *
+ * @author Tim Sparg
+ */
+@SpringBootApplication
+public class SpringJavaFormatCliApplication {
+
+ protected SpringJavaFormatCliApplication() {
+ }
+
+ public static void main(String[] args) {
+ SpringApplication application = new SpringApplication(SpringJavaFormatCliApplication.class);
+ application.setBannerMode(Banner.Mode.OFF);
+ application.setLogStartupInfo(false);
+ System.exit(SpringApplication.exit(application.run(args)));
+ }
+
+}
diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/SpringJavaFormatCommand.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/SpringJavaFormatCommand.java
new file mode 100644
index 00000000..fd206a69
--- /dev/null
+++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/SpringJavaFormatCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.javaformat.cli;
+
+import org.springframework.stereotype.Component;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Model.CommandSpec;
+import picocli.CommandLine.Spec;
+
+import io.spring.javaformat.cli.check.CheckCommand;
+import io.spring.javaformat.cli.format.ApplyCommand;
+
+/**
+ * Root picocli command.
+ *
+ * @author Tim Sparg
+ */
+@Component
+@Command(name = "spring-javaformat", mixinStandardHelpOptions = true, versionProvider = CliVersionProvider.class,
+ subcommands = { ApplyCommand.class, CheckCommand.class },
+ description = "Formats and checks Java source files")
+@PicocliManaged
+class SpringJavaFormatCommand implements Runnable {
+
+ @Spec
+ private CommandSpec spec;
+
+ @Override
+ public void run() {
+ this.spec.commandLine().usage(System.out);
+ }
+
+}
diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckCommand.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckCommand.java
new file mode 100644
index 00000000..7dff6fc3
--- /dev/null
+++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckCommand.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2017-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.javaformat.cli.check;
+
+import java.util.Properties;
+import java.util.concurrent.Callable;
+
+import org.springframework.stereotype.Component;
+import picocli.CommandLine.ArgGroup;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
+import picocli.CommandLine.Model.CommandSpec;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.ParameterException;
+import picocli.CommandLine.Spec;
+
+import io.spring.javaformat.cli.InputOptions;
+
+/**
+ * Check formatting and checkstyle violations.
+ *
+ * @author Tim Sparg
+ */
+@Component
+@Command(name = "check", mixinStandardHelpOptions = true,
+ description = "Check the codebase for formatting and checkstyle violations")
+public class CheckCommand implements Callable