以下の内容はhttps://kazuhira-r.hatenablog.com/entry/2026/03/12/161639より取得しました。


Spotless Maven PluginとPalantir Java Formatでソースコードをフォーマットする

これは、なにをしたくて書いたもの?

以前、Spotless Maven Pluginとgoogle-java-formatでソースコードのフォーマットをしてみました。

Spotless Maven Pluginとgoogle-java-formatで、ソースコードのフォーマットを行う - CLOVER🍀

今回はSpotless Maven PluginとPalantir Java Formatの組み合わせを使ってみたいと思います。

Palantir Java Format

Palantir Java FormatのGitHubリポジトリーはこちら。

GitHub - palantir/palantir-java-format: A modern, lambda-friendly, 120 character Java formatter. · GitHub

Palantir Java Formatは、google-java-formatをベースに作られた、Lambdaフレンドリーで120文字のJavaフォーマッターです。

google-java-formatはそれそれでよいのですが、以下の点が気になっていました。

  • インデントがスペース2つ
  • Lambdaやメソッドチェーンのフォーマットが微妙

インデントは調整可能ですし、他はそういうものだと割り切ってもいいかなと思っていたのですが、Palantir Java Formatの
README.mdを見てこのあたりが気になりました。

(1) google-java-format output:

private static void configureResolvedVersionsWithVersionMapping(Project project) {
    project.getPluginManager()
            .withPlugin(
                    "maven-publish",
                    plugin -> {
                        project.getExtensions()
                                .getByType(PublishingExtension.class)
                                .getPublications()
                                .withType(MavenPublication.class)
                                .configureEach(
                                        publication ->
                                                publication.versionMapping(
                                                        mapping -> {
                                                            mapping.allVariants(
                                                                    VariantVersionMappingStrategy
                                                                            ::fromResolutionResult);
                                                        }));
                    });
}

(1) palantir-java-format output:

private static void configureResolvedVersionsWithVersionMapping(Project project) {
    project.getPluginManager().withPlugin("maven-publish", plugin -> {
        project.getExtensions()
                .getByType(PublishingExtension.class)
                .getPublications()
                .withType(MavenPublication.class)
                .configureEach(publication -> publication.versionMapping(mapping -> {
                    mapping.allVariants(VariantVersionMappingStrategy::fromResolutionResult);
                }));
    });
}

(2) google-java-format output:

private static GradleException notFound(
        String group, String name, Configuration configuration) {
    String actual =
            configuration.getIncoming().getResolutionResult().getAllComponents().stream()
                    .map(ResolvedComponentResult::getModuleVersion)
                    .map(
                            mvi ->
                                    String.format(
                                            "\t- %s:%s:%s",
                                            mvi.getGroup(), mvi.getName(), mvi.getVersion()))
                    .collect(Collectors.joining("\n"));
    // ...
}

(2) palantir-java-format output:

private static GradleException notFound(String group, String name, Configuration configuration) {
    String actual = configuration.getIncoming().getResolutionResult().getAllComponents().stream()
            .map(ResolvedComponentResult::getModuleVersion)
            .map(mvi -> String.format("\t- %s:%s:%s", mvi.getGroup(), mvi.getName(), mvi.getVersion()))
            .collect(Collectors.joining("\n"));
    // ...
}

Palantir Java Format / Motivation & examples

こういうメソッドチェーンの場合、長くなった時に1行にまとめられてしまうとちょっと読みにくいのですが、

var foo = SomeType.builder().thing1(thing1).thing2(thing2).thing3(thing3).build();

Palantir Java Formatの場合はメソッドチェーンが80文字を超えると折り返してくれるようです。

var foo = SomeType.builder()
        .thing1(thing1)
        .thing2(thing2)
        .thing3(thing3)
        .build();

Palantir Java Format / Optimised for code review

なかなかよさそうだなと思ったので、使ってみることにしました。

Gradleプラグイン、Spotless、IntelliJプラグインとして使えるようです。今回はSpotlessのMavenプラグインと組み合わせて
使ってみます。

なお、こちらのエントリーを書いた時にもPalantir Java Formatの名前は載っていたのですが、どういうフォーマッターなのか
まったく見ていませんでした…。

Spotless Maven Pluginとgoogle-java-formatで、ソースコードのフォーマットを行う - CLOVER🍀

環境

今回の環境はこちら。

$ java --version
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Ubuntu-124.04)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Ubuntu-124.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.13 (39d686bd50d8e054301e3a68ad44781df6f80dda)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 25.0.2, vendor: Ubuntu, runtime: /usr/lib/jvm/java-25-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "6.8.0-101-generic", arch: "amd64", family: "unix"

準備

お題で使うソースコードは、こちらと同じものにします。

Spotless Maven Pluginとgoogle-java-formatで、ソースコードのフォーマットを行う - CLOVER🍀

Maven依存関係などはこちら。

    <properties>
        <maven.compiler.release>25</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.15.Final</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>com.diffplug.spotless</groupId>
                <artifactId>spotless-maven-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <java>
                        <palantirJavaFormat>
                            <version>2.89.0</version>
                            <style>PALANTIR</style>
                            <formatJavadoc>true</formatJavadoc>
                        </palantirJavaFormat>
                    </java>
                </configuration>
            </plugin>
        </plugins>
    </build>

Palantir Java Formatは設定済みですが、あとで説明します。

ソースコードを用意しましょう。

空のbeans.xml

src/main/resources/META-INF/beans.xml




フォーマットは最初から崩しておきます。

src/main/java/org/littlewings/formatter/App.java

package org.littlewings.formatter;

import org.jboss.logging.Logger;
import org.littlewings.formatter.rest.RestApplication;


import jakarta.ws.rs.SeBootstrap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class App {
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
private 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!!"; } } }

ちょっと見た目的にわかりにくいですが、簡単なJakarta RESTful Web Servicesを使ったアプリケーションです。

動作確認しておきます。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.formatter.App

OKですね。

$ curl localhost:8080/hello?word=RESTEasy
Hello RESTEasy!!

Spotless Maven PluginでPalantir Java Formatを使う

では、Spotless Maven PluginとPalantir Java Formatについて見ていきます。こういう設定をしていました。

                <configuration>
                    <java>
                        <palantirJavaFormat>
                            <version>2.89.0</version>
                            <style>PALANTIR</style>
                            <formatJavadoc>true</formatJavadoc>
                        </palantirJavaFormat>
                    </java>
                </configuration>

ちなみに、最小構成としてはこちらになります。

                <configuration>
                    <java>
                        <palantirJavaFormat />
                    </java>
                </configuration>

まずは確認。

$ mvn spotless:check

当然ですが、フォーマットがめちゃくちゃなので怒られます。

[ERROR] Failed to execute goal com.diffplug.spotless:spotless-maven-plugin:3.3.0:check (default-cli) on project palantir-java-format-spotless-example: The following files had format violations:
[ERROR]     src/main/java/org/littlewings/formatter/App.java
[ERROR]         @@ -1,35 +1,30 @@
[ERROR]          package·org.littlewings.formatter;
[ERROR]
[ERROR]         -import·org.jboss.logging.Logger;
[ERROR]         -import·org.littlewings.formatter.rest.RestApplication;
[ERROR]         -
[ERROR]         -
[ERROR]          import·jakarta.ws.rs.SeBootstrap;
[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]         -static·void·main(String...·args)·throws·ExecutionException,·InterruptedException·{
[ERROR]         -Logger·logger·=
[ERROR]         -········Logger.getLogger(App.class);
[ERROR]         +····static·void·main(String...·args)·throws·ExecutionException,·InterruptedException·{
[ERROR]         +········Logger·logger·=·Logger.getLogger(App.class);
[ERROR]
[ERROR]         -SeBootstrap.Configuration·configuration·=················SeBootstrap
[ERROR]         -.Configuration.builder().host("0.0.0.0").port(8080).build();
[ERROR]         +········SeBootstrap.Configuration·configuration·=
[ERROR]         +················SeBootstrap.Configuration.builder().host("0.0.0.0").port(8080).build();
[ERROR]
[ERROR]         -········SeBootstrap.Instance
[ERROR]         -················instance·=
[ERROR]         -SeBootstrap.start(new·RestApplication(),·configuration)
[ERROR]         -.toCompletableFuture()
[ERROR]         -.get();
[ERROR]         +········SeBootstrap.Instance·instance·=·SeBootstrap.start(new·RestApplication(),·configuration)
[ERROR]         +················.toCompletableFuture()
[ERROR]         +················.get();
[ERROR]
[ERROR]         -logger.info("server·startup.");
[ERROR]         +········logger.info("server·startup.");
[ERROR]
[ERROR]         -while·(true)·{
[ERROR]         -TimeUnit.SECONDS.sleep(5L);
[ERROR]         -}
[ERROR]         +········while·(true)·{
[ERROR]         +············TimeUnit.SECONDS.sleep(5L);
[ERROR]         +········}
[ERROR]
[ERROR]          ········/*
[ERROR]          ·········*·instance.stop().toCompletableFuture().get();
[ERROR]          ·········*/
[ERROR]         -}
[ERROR]         +····}
[ERROR]     ... (1 more lines that didn't fit)
[ERROR] Violations also present in:
[ERROR]     src/main/java/org/littlewings/formatter/rest/RestApplication.java
[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

フォーマットされました。

[INFO] --- spotless:3.3.0:apply (default-cli) @ palantir-java-format-spotless-example ---
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::staticFieldBase has been called by com.diffplug.spotless.java.ModuleHelper (file:/$HOME/.m2/repository/com/diffplug/spotless/spotless-lib/4.4.0/spotless-lib-4.4.0.jar)
WARNING: Please consider reporting this to the maintainers of class com.diffplug.spotless.java.ModuleHelper
WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release
[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/to/src/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

確認してみましょう。

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 {
    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
    private 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はデフォルトでvalidateフェーズにマッピングされているので、以下のように設定すると
mvn verifyspotless:checkが動作するようになります。

            <plugin>
                <groupId>com.diffplug.spotless</groupId>
                <artifactId>spotless-maven-plugin</artifactId>
                <version>3.3.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>check</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <java>
                        <palantirJavaFormat>
                            <version>2.89.0</version>
                            <style>PALANTIR</style>
                            <formatJavadoc>true</formatJavadoc>
                        </palantirJavaFormat>
                    </java>
                </configuration>
            </plugin>

個人的にはmvn compile時にspotless:applyしてしまう、でいいのではないかと思いますが。

                <executions>
                    <execution>
                        <goals>
                            <goal>apply</goal>
                        </goals>
                        <phase>compile</phase>
                    </execution>
                </executions>

ただ、この設定はテストコードもフォーマットしようとするので、mvn test-compileの前に動作してしまう点には注意です。

設定を確認する

ところでいきなりこちらを載せましたが、バージョンの指定とformatJavadoc以外はデフォルトです。

                        <palantirJavaFormat>
                            <version>2.89.0</version>
                            <style>PALANTIR</style>
                            <formatJavadoc>true</formatJavadoc>
                        </palantirJavaFormat>

formatJavadocは文字通りJavadocをフォーマットするかどうかですね。デフォルトはfalseです。

設定できるものは、実は多くありません。というか、上記がすべてです。

https://github.com/diffplug/spotless/blob/maven/3.3.0/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/PalantirJavaFormat.java

https://github.com/diffplug/spotless/blob/maven/3.3.0/lib/src/main/java/com/diffplug/spotless/java/PalantirJavaFormatStep.java

styleにはPALANTIR(デフォルト)以外にGOOGLEAOSPが指定できます。

GOOGLEを指定するとなにが起こるのか?と思いますが、インデントが2になります。

                        <palantirJavaFormat>
                            <version>2.89.0</version>
                            <style>GOOGLE</style>
                            <formatJavadoc>true</formatJavadoc>
                        </palantirJavaFormat>
public class App {
  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();
     */
  }
}

こう見ると、スタイルの差はインデントと行あたりの最大長ですね。

palantir-java-format/palantir-java-format-spi/src/main/java/com/palantir/javaformat/java/JavaFormatterOptions.java at 2.89.0 · palantir/palantir-java-format · GitHub

AOSPを選ぶとインデントは4になりますが、行あたりの最大長が100になります。

google-format-javaと比べる

少しgoogle-format-javaのフォマート結果と比べてみましょうか。

こちらがPalantir Java Formatでフォーマットした結果でした。

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 {
    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();
         */
    }
}

google-java-formatに変えてみます。

                        <googleJavaFormat>
                            <version>1.35.0</version>
                            <style>GOOGLE</style>
                            <reorderImports>true</reorderImports>
                            <formatJavadoc>true</formatJavadoc>
                        </googleJavaFormat>

フォーマットを実行。

$ mvn spotless:apply

こうなりました。

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 {
  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();
     */
  }
}

インデント以外にも、このあたりに差が出ますね。

    // Palantir Java Format
        SeBootstrap.Instance instance = SeBootstrap.start(new RestApplication(), configuration)
                .toCompletableFuture()
                .get();


    // google-java-format
    SeBootstrap.Instance instance =
        SeBootstrap.start(new RestApplication(), configuration).toCompletableFuture().get();

こんなところでしょうか。

おわりに

Spotless Maven PluginとPalantir Java Formatでソースコードをフォーマットしてみました。

Javaのフォーマッターはなかなか悩ましいのですが、あまりいろいろチューニングしたいわけでもないので、Palantir Java Format
くらいのフォーマットで個人的にはよさそうな気がします。

フォーマッターを使う時はこれかな、と。




以上の内容はhttps://kazuhira-r.hatenablog.com/entry/2026/03/12/161639より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14