Formatting Java
I tried setting up linting in Java recently, and it was much harder than I expected.
In many discussions about linting on the Internet, I often saw people saying: “Just use ESLint and Prettier, done.” In the JavaScript and Python ecosystems, it is quite simple, and additionally these languages are among the most popular, so that’s why there is more information about linting and formatting for these two languages. I suspect part of the reason is that these languages are dynamic, so the compiler doesn’t solve as many issues as the compiler in a static language like Java does.
I think the other reason is that Java historically leaned into using linters that were built into the IDE, so Eclipse and then IntelliJ. Usually people intuitively format with a shortcut before committing, and it’s all fine. It’s quite rare for me to see a project that is using formatting for Java in the CI. Most small and mid-sized projects I saw, at least, don’t have one, because usually there are not enough problems to warrant it. And in the big ones, there was some kind of solution like this. I usually wasn’t the person that set it up, so I didn’t dig into it.
When LLMs generate or modify Java code, they sometimes introduce subtle formatting issues:
- Trailing whitespace on empty lines, indentation on empty lines
- Inconsistent parameter alignment, LLMs tend to use continuation indentation (one extra level), like ecplise, for multi-line parameter lists, while IntelliJ aligns parameters with the opening parenthesis.
- Adds spaces after commas in multiline parameter lists.
These things create noise in pull requests, 10 to 20 extra changed lines that have nothing to do with the actual change. It happens most often when AI creates new files, and in another PR AI fixes these errors, giving additional lines in git diff. IntelliJ’s formatter fixes all of these if you run it, but sometimes when working with AI, I just review, commit, and push without reformatting.
I wanted to fix this and learn more about linting/reformatting ecosystem in java. I wrote this down to have a note to come back to in the future.
Misconceptions
When I brought this up with fellow Java developers, they assumed it was a solved problem. “Just use .editorconfig,” one said. Another suggested Prettier.
.editorconfig works for the most basic rules — indentation style, line endings, trailing whitespace. But the specific formatting rules I export from IntelliJ into .editorconfig format aren’t respected by any external tool I could find, these rules start with ij_java. I just saw that IntelliJ allows exporting their rules in editor config, but doesn’t it allow importing them back? So even Intellij doesn’t work with its own exported rules.
Prettier has a Java plugin, but it doesn’t follow IntelliJ’s formatting conventions. It would reformat everything into its own style, which defeats the purpose.
What I Wanted
- CI verification - a check that fails if code doesn’t match the project’s style. Not auto-fixing in CI (that creates commits people forget to pull), just verification.
- Local auto-fixing - something that reformats code before it’s pushed, ideally without requiring extra effort from developers.
Possible solutions
CI verification
Adopt Google Java Format or Palantir style. These have excellent tooling and Spotless integration. But migrating an existing team away from IntelliJ’s default style is a hard sell, unnecessary work if I can make IntelliJ’s style work.
Use Qodana - JetBrains own CI tool for code quality. It has a free tier, and it understands IntelliJ’s formatting rules natively (I think, I didn’t chceck actually). But its license requires sending data to JetBrains, and I didn’t want to go through a legal review for a formatting tool. That process would take months and qodana would be rejected in the end anyway.
Run headless IntelliJ Community Edition in CI - It would work, but it’s a 600MB download. Even with caching, it felt too heavy for what should be a simple check and I didn’t want to do caching. Also, I wasn’t fully confident about the licensing implications in a corporate CI environment, so same problem as with Qodana.
Local fixing
- make intellij autoformat on save or commit - I wanted to store this setting in our VCS, and it’s even a highly requested feature from IntelliJ, but it’s only stored in IntelliJ configuration, which isn’t usually shared over git. You have to create an instruction for each member to set it once, and it should be okay for now. This will also require instruction on how to set up VS Code with this style. Additional thing that you can do is re-import rules that you exported back into Intellij to make sure it follows them correctly, but I didn’t teset it.
- configure Spotless to apply fixes
- run spotless as part of pre-commit hook.
- could plug it into the verify step so that when people would be building their project and running the test with Maven after modifying it, it would also run and modify the project.
Picked solution
I landed on Spotless, it’s the most popular. Spotless itself isn’t a formatter, it’s a harness that delegates to other formatters. The key insight is that IntelliJ can export its formatting rules in Eclipse format, and there is Spotless Eclipse formatter.
This gives me a CI check that’s fast and doesn’t require downloading heavy IDE installations.
For local auto-fixing, I enabled IntelliJ’s built-in “Reformat code” option in the commit dialog. It’s not perfect, this setting is stored in IntelliJ’s local configuration and can’t be shared via version control, I’ll have write a short setup guide for the team. But it’s simplest and requires no additional tooling.
The exported Eclipse rules aren’t 100% compatible with IntelliJ’s internal rules. I suspect that some rules are just incompatible, you can’t reproduce the same effect that Intellij formatter produces, so I had to remove 3 rules.
Interesting thing was that AI wasn’t very helpful when debugging and fixing some incompatibility problems with Eclipse rule set format. I guess it isn’t popular enough to be trained on extensively. It is always interesting to find a case in which AI doesn’t do well, as sometimes I’m very surprised by esoteric use cases which it’s quite good at.
I’m betting that the skipped rules won’t cause any real-world issues, these are edge cases that wont happen, we would have to also forget to format with Intellij which also happens rarely. If they will start happening more often I will reimport Intellij rules in Eclipse format to Intellij and see if it will help.
Future posts
I think I can create a tool that auto fixes issues caught by static analysis tools like SonarQub, I will try to create some meta programming solution. I think autofixing issues like these can be precious part of “verify” part after AI writes seomthing, making it easier to review code afterward. After all, all automation is “free” while human review time is expensive. Maybe I can just send SonarQube raport to LLM? I would like to create my own static analysis tool that would focus on checking if code has loose coupling and is modularized properly. I think this is the most important part, and if it’s too complicated or the diff touches too many modules at once, I can tell LLM to recreate it from scratch. Or if it created too big of a PR.