GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — The routes format the same file differently: formatDiff on the JVM, ranges, and the order of fixing imports · Issue #219 · openjavaformat/open-java-format

The routes format the same file differently: formatDiff on the JVM, ranges, and the order of fixing imports #219

New issue
Copy link
New issue
Copy link
Open
#220
Open
The routes format the same file differently: formatDiff on the JVM, ranges, and the order of fixing imports#219
#220
Copy link
Labels
bugSomething isn't workingSomething isn't working

Description

@abashev
abashev
opened on Nov 12, 2026
Issue body actions

What happens

The same file comes out differently depending on the route that formats it. Found by running every route on the same input with 21.0.2 on JDK 21, the native binary for macOS aarch64 included, over 3,865 real files: 3,000 from a set of local repositories and 865 from java.base of JDK 21.

1. formatDiff on the JVM does not reflow long strings; with the native binary it does. A changed line

    String s = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";

becomes, without openjavaformat.native.formatter,

    String s =
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";

and with it

    String s = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et"
            + " dolore magna aliqua.";

The first fails the check of the GitHub Action and the pre-commit hook (--dry-run --set-exit-if-changed with the native binary); the second passes.

2. Formatting a range reflows long strings outside it. IntelliJ IDEA, with the Java-based formatter and the native one alike, and formatDiff with the native binary: once a line in the ranges needs wrapping, every long string in the file is reflowed, on lines nobody touched. Only changed is on a changed line here:

 class S {
-    String outside = "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat sed.";
+    String outside = "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo"
+            + " consequat sed.";

-    String changed = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna.";
+    String changed =
+            "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et"
+                    + " dolore magna.";
 }

Over every line with a string literal and more than 90 characters in 600 of the files, formatted one line at a time, the reflow changed 11 results, and in 2 of them it changed strings outside the code that formatting the line rewrote.

3. The Maven plugin and the Spotless step fix the imports before formatting; the command line, the native binary, the GitHub Action, the pre-commit hook and the playground fix them after it. The result differs when an import that is removed has a comment. From java/nio/charset/CharsetEncoder.java in JDK 21, where import java.nio.charset.CoderMalfunctionError; // javadoc is removed:

before (Maven plugin, Spotless step)   after (command line, native binary)
import java.lang.ref.WeakReference;    import java.lang.ref.WeakReference;
import java.nio.Buffer;                // javadoc
import java.nio.ByteBuffer;            import java.nio.Buffer;
import java.nio.CharBuffer;            import java.nio.ByteBuffer;
// javadoc                             import java.nio.CharBuffer;

A comment right under a removed import gets a blank line between it and the class before, and stays on the class after. And import java.util.List;; followed by another import, which javac 21 rejects with --release 21 and accepts with a warning below it: the Maven plugin and the Spotless step drop the semicolon and format the file, the command line fails with extraneous semicolon.

This is rare: 1 of the 3,865 files, as they are and with an unused import added to each. Each order passes its check on what the other wrote, so no build fails on it; it decides what a file looks like after it is first formatted.

Why

  • 1. The Gradle plugin loads FormatterServiceImpl into its own JVM, and its getFormatReplacements calls Formatter.getFormatReplacements, which never reflows strings. The bootstrapping and native services run Main --output-replacements, where FormatFileCallable reflows them after the replacements.
  • 2. That reflow gets the whole file, and its result goes back as one replacement of the whole file. The ranges only decide whether to reflow at all, through StringWrapper.linesNeedWrapping, which compares the ranges of the input with the lines of the formatted text.
  • 3. Formatter.formatSourceAndFixImports, behind FormatterService.formatSourceReflowStringsAndFixImports, runs ImportOrderer, RemoveUnusedImports, formatSource, StringWrapper; FormatFileCallable.formatFile runs formatSource, RemoveUnusedImports, ImportOrderer, StringWrapper. ImportOrderer moves a comment along with its import, and RemoveUnusedImports leaves the comment of an import it removes behind, so the order decides where that comment ends up. The command line has to format first: the ranges of --lines and --offset are offsets into the input.

No test runs the in-process service against the others: FormatterServicesTest and FormatDiffTest cover the bootstrapping and native services only.

Proposal

  • One method formats the ranges and reflows the strings, for --output-replacements and for FormatterServiceImpl.getFormatReplacements.
  • With ranges, StringWrapper reflows only the literals in the code that formatting the ranges rewrites, moving the ranges along with each of its passes. Without ranges it stays as it is, so whole-file formatting does not change.
  • formatSourceAndFixImports fixes the imports after formatting, as the command line does.
  • Tests hold the in-process service to what the command line prints, and FormatterServicesTest and FormatDiffTest run the in-process formatter too.

Not here

  • Spotless' own palantirJavaFormat() step runs the formatter through Spotless' code, which never reflows strings. That is Spotless' to change; the docs say so.
  • The Eclipse plugin formats with Formatter.getFormatReplacements and does not reflow strings either.
  • open-java-format --lines without --output-replacements still reflows the whole file.

Activity

  1. abashev
    added
    bugSomething isn't workingSomething isn't working
    on Nov 12, 2026
  2. abashev
    added a commit that references this issue on Nov 12, 2026

    Expect what the command line prints for a comment in a module name

    b06ccee
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.