これは、なにをしたくて書いたもの?
以前、Formatter Maven Pluginを使ってソースコードのフォーマットをしてみました。
Formatter Maven Pluginで、ソースコードのフォーマットを行う - CLOVER🍀
今回はSpotless Maven Pluginとgoogle-java-formatでソースコードのフォーマットを行ってみます。
google-java-format
先にgoogle-java-formatから載せておきましょう。google-java-formatのGitHubリポジトリーはこちら。
GitHub - google/google-java-format: Reformats Java source code to comply with Google Java Style.
google-java-formatは、JavaソースコードをGoogle Java Style Guideに準拠させるプログラムです。
google-java-format - IntelliJ IDEs Plugin | Marketplace
各種ビルドツールとのインテグレーションは、サードパーティーの提供に任されているようです。
google-java-format / Third-party integrations
Apache Maven向けの場合は、Spotless Maven Pluginがそのひとつとして挙げられています。
https://github.com/diffplug/spotless/tree/main/plugin-maven
その他にもMavenプラグインが紹介されていますが、実質的な選択肢はSpotless Maven Pluginになるように思います。
Spotless Maven Plugin
GitHub - diffplug/spotless: Keep your code spotless
Spotless自体は様々な言語のソースコードのフォーマッターで、Apache Maven、Gradleのプラグインとして動作します。
https://github.com/diffplug/spotless/tree/maven/2.44.5/plugin-maven
Quickstartを見るとだいたい使い方の雰囲気はわかる気がします。
Spotless: Keep your code spotless with Maven / Quickstart
実行にはJava 11以上が必要なようです。
Spotless requires Maven to be running on JRE 11+. To use JRE 8, go back to 2.30.0 or older.
対応言語は多く、以下になっています。
- Java (google-java-format, eclipse jdt, prettier, palantir-java-format, formatAnnotations, cleanthat)
- Groovy (eclipse groovy)
- Kotlin (ktfmt, ktlint, diktat, prettier)
- Scala (scalafmt)
- C/C++ (eclipse cdt, clang-format)
- Python (black)
- Antlr4 (antlr4formatter)
- Sql (dbeaver)
- Maven Pom (sortPom)
- Markdown (flexmark)
- Typescript (tsfmt, prettier, ESLint, Biome)
- Javascript (prettier, ESLint, Biome)
- JSON (simple, gson, jackson, Biome, jsonPatch)
- YAML
- Gherkin
- Go
- RDF
- Protobuf (buf, clang-format)
- Multiple languages
- Prettier (plugins, npm detection, .npmrc detection, caching npm install results)
- eclipse web tools platform
- Biome (binary detection, config file, input language)
とはいえ、実質的に使うのはJava(もしくはJava VM上の言語)に関するものだと思いますが。
デフォルトではspotless:check
ゴールがApache Mavenのvalidate
フェーズにマッピングされているようです。
Spotless: Keep your code spotless with Maven / Binding to maven phase
つまり、以下のように書いておくとmvn verify
で動作します。
<executions> <execution> <goals> <goal>check</goal> </goals> </execution> </executions>
compile
フェーズでspotless:check
を実行する例がサンプルとして載せられています。
Spotless Maven Pluginのゴールについて説明しているセクションは特になさそうなのですが、spotless:check
で確認、
spotless:apply
で修正となるようです(ドキュメントの最初に載っています)。
user@machine repo % mvn spotless:check [ERROR] > The following files had format violations: [ERROR] src\main\java\com\diffplug\gradle\spotless\FormatExtension.java [ERROR] -\t\t····if·(targets.length·==·0)·{ [ERROR] +\t\tif·(targets.length·==·0)·{ [ERROR] Run 'mvn spotless:apply' to fix these violations. user@machine repo % mvn spotless:apply [INFO] BUILD SUCCESS user@machine repo % mvn spotless:check [INFO] BUILD SUCCESS
Javaで使う
Javaでの使い方ですが、フォーマットはgoogle-java-format、palantir-java-format、eclipse jdtの3種から選びます。
Spotless: Keep your code spotless with Maven / Languages / Java
今回はgoogle-java-formatを使います。設定はこちら。
Spotless: Keep your code spotless with Maven / Languages / Java / google-java-format
設定項目自体は多くないですが、詳しくはソースコードを見てという感じみたいです。
また言語を問わない設定はこちらです。
Spotless: Keep your code spotless with Maven / Language independent / Generic steps
だいたい設定などは見た気がするので、とりあえず使っていってみましょう。
お題となるソースコードはFormatter Maven Pluginの時に使ったものをベースにすることにします。
Formatter Maven Pluginで、ソースコードのフォーマットを行う - CLOVER🍀
環境
今回の環境はこちら。
$ java --version openjdk 21.0.7 2025-04-15 OpenJDK Runtime Environment (build 21.0.7+6-Ubuntu-0ubuntu124.04) OpenJDK 64-Bit Server VM (build 21.0.7+6-Ubuntu-0ubuntu124.04, mixed mode, sharing) $ mvn --version Apache Maven 3.9.10 (5f519b97e944483d878815739f519b2eade0a91d) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 21.0.7, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "6.8.0-60-generic", arch: "amd64", family: "unix"
サンプルアプリケーションを作成する
では、簡単なサンプルアプリケーションを作成します。
先ほど書いたように、ベースはこちらです。Jakarta RESTful Web Services+Jakarta Contexts and Dependency Injectionな
Javaアプリケーションを、Java SE環境で動かすことにしましょう。
Formatter Maven Pluginで、ソースコードのフォーマットを行う - CLOVER🍀
Maven依存関係などはこちら。
<properties> <maven.compiler.release>21</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-undertow-cdi</artifactId> <version>6.2.12.Final</version> </dependency> </dependencies>
空のbeans.xml
。
src/main/resources/META-INF/beans.xml
ソースコードはこちら。フォーマット後です。
src/main/java/org/littlewings/formatter/App.java
package org.littlewings.formatter; import jakarta.ws.rs.SeBootstrap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.jboss.logging.Logger; import org.littlewings.formatter.rest.RestApplication; public class App { public static void main(String... args) throws ExecutionException, InterruptedException { Logger logger = Logger.getLogger(App.class); SeBootstrap.Configuration configuration = SeBootstrap.Configuration.builder().host("0.0.0.0").port(8080).build(); SeBootstrap.Instance instance = SeBootstrap.start(new RestApplication(), configuration).toCompletableFuture().get(); logger.info("server startup."); while (true) { TimeUnit.SECONDS.sleep(5L); } /* * instance.stop().toCompletableFuture().get(); */ } }
src/main/java/org/littlewings/formatter/rest/RestApplication.java
package org.littlewings.formatter.rest; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application; import java.util.Set; @ApplicationPath("/") public class RestApplication extends Application { @Override public Set<Class<?>> getClasses() { return Set.of(HelloResource.class); } }
src/main/java/org/littlewings/formatter/rest/HelloResource.java
package org.littlewings.formatter.rest; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; import org.littlewings.formatter.service.MessageService; @Path("/hello") @ApplicationScoped public class HelloResource { @Inject MessageService messageService; @GET @Produces(MediaType.TEXT_PLAIN) public String message(@QueryParam("word") String word) { return messageService.create(word); } }
src/main/java/org/littlewings/formatter/service/MessageService.java
package org.littlewings.formatter.service; import jakarta.enterprise.context.ApplicationScoped; @ApplicationScoped public class MessageService { public String create(String word) { if (word != null) { return String.format("Hello %s!!", word); } else { return "Hello World!!"; } } }
動作確認。
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.formatter.App
OKですね。
$ curl localhost:8080/hello?word=RESTEasy Hello RESTEasy!!
ではここで、適当にソースコードのフォーマットを崩してみます。
src/main/java/org/littlewings/formatter/App.java
package org.littlewings.formatter; import jakarta.ws.rs.SeBootstrap; import org.jboss.logging.Logger; import org.littlewings.formatter.rest.RestApplication; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class App { public static void main(String... args) throws ExecutionException, InterruptedException { Logger logger = Logger.getLogger(App.class); SeBootstrap.Configuration configuration = SeBootstrap.Configuration.builder().host("0.0.0.0").port(8080) .build(); SeBootstrap.Instance instance = SeBootstrap.start(new RestApplication(), configuration).toCompletableFuture() .get(); logger.info("server startup."); while (true){TimeUnit.SECONDS.sleep(5L);} /* * instance.stop().toCompletableFuture().get(); */ } }
src/main/java/org/littlewings/formatter/rest/RestApplication.java
package org.littlewings.formatter.rest; import java.util.Set; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application; @ApplicationPath("/") public class RestApplication extends Application { @Override public Set<Class<?>> getClasses() { return Set.of(HelloResource.class); } }
src/main/java/org/littlewings/formatter/rest/HelloResource.java
package org.littlewings.formatter.rest; import jakarta.enterprise.context.ApplicationScoped; import org.littlewings.formatter.service.MessageService; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; @Path("/hello") @ApplicationScoped public class HelloResource { @Inject MessageService messageService; @GET @Produces(MediaType.TEXT_PLAIN) public String message( @QueryParam("word") String word ) { return messageService.create(word); } }
src/main/java/org/littlewings/formatter/service/MessageService.java
package org.littlewings.formatter.service; import jakarta.enterprise.context.ApplicationScoped; @ApplicationScoped public class MessageService { public String create(String word) { if (word != null) {return String.format("Hello %s!!", word); } else { return "Hello World!!"; } } }
Spotless Maven Pluginとgoogle-java-formatを組み込む
それでは、Spotless Maven Pluginとgoogle-java-formatを組み込んでみます。
<build> <plugins> <plugin> <groupId>com.diffplug.spotless</groupId> <artifactId>spotless-maven-plugin</artifactId> <version>2.44.5</version> <configuration> <java> <googleJavaFormat> <version>1.27.0</version> <reorderImports>true</reorderImports> <formatJavadoc>true</formatJavadoc> </googleJavaFormat> <indent> <tabs>true</tabs> <spacesPerTab>2</spacesPerTab> </indent> <indent> <spaces>true</spaces> <spacesPerTab>4</spacesPerTab> </indent> </java> </configuration> </plugin> </plugins> </build>
Java向けの設定で、google-java-formatを使うように設定します。
<configuration> <java> <googleJavaFormat> <version>1.27.0</version> <reorderImports>true</reorderImports> <formatJavadoc>true</formatJavadoc> </googleJavaFormat> ... </java> </configuration>
最小構成だと以下でもいいのですが、google-java-formatのバージョンを明示的に指定したり、import
の並び替えだったり
Javadocの設定を入れたりしています。
<configuration> <java> <googleJavaFormat/> </java> </configuration>
version
は明示的に指定しない場合は使っているJava VMに合わせたものを選んでくれるようですが、最新のものを選んでくれる
わけでもなさそうなので明示的に書くようにしました。
この設定は、インデントを2から4に変更する設定です。
<indent> <tabs>true</tabs> <spacesPerTab>2</spacesPerTab> </indent> <indent> <spaces>true</spaces> <spacesPerTab>4</spacesPerTab> </indent>
Google Java Styleでは空白はスペース2つなのですが、ここは変えてみました。まあ、もはやこういうのは変えない方がいいのでは
という気もしますけどね。
設定はこちらを参考にしています。
spotless:check
で確認。
$ mvn spotless:check
結果。まあ怒られますね。
[ERROR] Failed to execute goal com.diffplug.spotless:spotless-maven-plugin:2.44.5:check (default-cli) on project spotless-maven-plugin-example: The following files had format violations: [ERROR] src/main/java/org/littlewings/formatter/App.java [ERROR] @@ -1,26 +1,26 @@ [ERROR] package·org.littlewings.formatter; [ERROR] [ERROR] import·jakarta.ws.rs.SeBootstrap; [ERROR] - [ERROR] -import·org.jboss.logging.Logger; [ERROR] -import·org.littlewings.formatter.rest.RestApplication; [ERROR] - [ERROR] import·java.util.concurrent.ExecutionException; [ERROR] import·java.util.concurrent.TimeUnit; [ERROR] +import·org.jboss.logging.Logger; [ERROR] +import·org.littlewings.formatter.rest.RestApplication; [ERROR] [ERROR] public·class·App·{ [ERROR] -public·static·void·main(String...·args)·throws·ExecutionException,·InterruptedException·{ [ERROR] -Logger·logger·=·Logger.getLogger(App.class); [ERROR] +····public·static·void·main(String...·args)·throws·ExecutionException,·InterruptedException·{ [ERROR] +········Logger·logger·=·Logger.getLogger(App.class); [ERROR] [ERROR] -SeBootstrap.Configuration·configuration·=·SeBootstrap.Configuration.builder().host("0.0.0.0").port(8080) [ERROR] -.build(); [ERROR] +········SeBootstrap.Configuration·configuration·= [ERROR] +················SeBootstrap.Configuration.builder().host("0.0.0.0").port(8080).build(); [ERROR] [ERROR] -SeBootstrap.Instance·instance·=·SeBootstrap.start(new·RestApplication(),·configuration).toCompletableFuture() [ERROR] -.get(); [ERROR] +········SeBootstrap.Instance·instance·= [ERROR] +················SeBootstrap.start(new·RestApplication(),·configuration).toCompletableFuture().get(); [ERROR] [ERROR] -logger.info("server·startup."); [ERROR] +········logger.info("server·startup."); [ERROR] [ERROR] -while·(true){TimeUnit.SECONDS.sleep(5L);} [ERROR] +········while·(true)·{ [ERROR] +············TimeUnit.SECONDS.sleep(5L); [ERROR] +········} [ERROR] [ERROR] ········/* [ERROR] ·········*·instance.stop().toCompletableFuture().get(); [ERROR] src/main/java/org/littlewings/formatter/rest/RestApplication.java [ERROR] @@ -1,13 +1,13 @@ [ERROR] package·org.littlewings.formatter.rest; [ERROR] [ERROR] -import·java.util.Set; [ERROR] import·jakarta.ws.rs.ApplicationPath; [ERROR] import·jakarta.ws.rs.core.Application; [ERROR] +import·java.util.Set; [ERROR] [ERROR] @ApplicationPath("/") [ERROR] ... (10 more lines that didn't fit) [ERROR] Violations also present in: [ERROR] src/main/java/org/littlewings/formatter/rest/HelloResource.java [ERROR] src/main/java/org/littlewings/formatter/service/MessageService.java [ERROR] Run 'mvn spotless:apply' to fix these violations. [ERROR] -> [Help 1]
表示しきれなかったものは省略されているようです。
spotless:apply
でこれを修正します。
$ mvn spotless:apply
4ファイルフォーマットされたことが確認できました。
[INFO] --- spotless:2.44.5:apply (default-cli) @ spotless-maven-plugin-example --- [INFO] clean file: /path/to/src/main/java/org/littlewings/formatter/App.java [INFO] clean file: /path/to/src/main/java/org/littlewings/formatter/rest/RestApplication.java [INFO] clean file: /path/tosrc/main/java/org/littlewings/formatter/rest/HelloResource.java [INFO] clean file: /path/to/src/main/java/org/littlewings/formatter/service/MessageService.java [INFO] Spotless.Java is keeping 4 files clean - 4 were changed to be clean, 0 were already clean, 0 were skipped because caching determined they were already clean
結果は最初に戻るだけなので省略します。
コンパイル時に動作するようにする
もう少し設定を変えてみましょう。コンパイル時に実行するようにするのは、executions / execution
を追加します。
<plugins> <plugin> <groupId>com.diffplug.spotless</groupId> <artifactId>spotless-maven-plugin</artifactId> <version>2.44.5</version> <executions> <execution> <goals> <goal>check</goal> </goals> <phase>compile</phase> </execution> </executions> <configuration> <java> <googleJavaFormat> <version>1.27.0</version> <reorderImports>true</reorderImports> <formatJavadoc>true</formatJavadoc> </googleJavaFormat> <indent> <tabs>true</tabs> <spacesPerTab>2</spacesPerTab> </indent> <indent> <spaces>true</spaces> <spacesPerTab>4</spacesPerTab> </indent> </java> </configuration> </plugin>
これでmvn compile
でチェックが行われ、フォーマットされていないファイルがあるとビルドが失敗するようになります。
$ mvn compile
フォーマットする対象を設定する
これはincludes / include
で行います。
こんな感じですね。
<java> <includes> <include>src/main/java/**/*.java</include> <include>src/test/java/**/*.java</include> </includes> ... <java>
こんなところでしょうか。
おわりに
Spotless Maven Pluginとgoogle-java-formatでソースコードのフォーマットを行ってみました。
この手のフォーマッターですが、微妙に結果が気になったりして個人的にあまり扱わないところがあったりしたのですが、
最近のAI事情もあって、そこを気にしても仕方がなくなるんだろうなと思い始めていたりします。
だとすると、インデントにこだわるなという話なのですが(笑)。
あと、設定変えるとIntelliJ IDEAのgoogle-java-formatと動きが合わなくなるとかありますが…。
というか、言語にフォーマッターが入ってくれるのがモメるところがなくて1番いい気がしますけどね。
まあ、今から入ることはそうそうないと思うので、もう合わせていきましょうか。
そういえば、Springにもフォーマッターがありましたね。