Refactoring Java Method Signatures with IntelliJ's Structural Search and Replace
IntelliJ's Structural Search and Replace matches code by syntax tree, not text. Learn to refactor a Java method signature across hundreds of call sites with type-safe templates, preview diffs, and shareable XML — while understanding where PSI parsing falls short.
09 Dec 2025, 23:40 UTC

The problem: regex can't see types
You've inherited a Java codebase where a utility method oldMethod(String) appears in 200+ call sites across five modules. The signature needs to become newMethod(String, Locale) with a default locale argument. A plain text search-and-replace breaks generics, misses overloaded variants, and can't insert the new parameter in the right position. Writing a custom IntelliJ plugin is overkill for a one-time migration.
IntelliJ IDEA's Structural Search and Replace (SSR) solves this by matching against the IDE's Program Structure Interface (PSI) — the same parsed representation the compiler uses. That means the pattern oldMethod($arg$) understands that $arg$ is an expression of type String, not just a text fragment.
Why SSR beats regex for code refactoring
Regular expressions operate on characters. SSR operates on syntax trees. When you write a template like oldMethod($arg$), IntelliJ resolves $arg$ as a variable placeholder with optional type constraints. The engine then finds every call where the receiver type, method name, and argument count match — regardless of formatting, line breaks, or intervening comments.
Key advantages over text-based approaches:
- Type-aware matching:
$arg$ instanceof Stringrestricts matches to string-typed expressions. - Scope awareness: The same template distinguishes between a method call, a method reference, and a lambda argument.
- Instant preview: Because SSR uses the project index, the match list appears in seconds even on large codebases.
- Undoable apply: The replacement runs as a single refactoring transaction — Ctrl+Z rolls back every file.
Worked example: migrating oldMethod to newMethod
Assume the legacy signature:
public static Result oldMethod(String input) { ... }Target signature (with a sensible default):
public static Result newMethod(String input, Locale locale) { ... }Step 1 — Open SSR dialog
Press Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (macOS), then click the Structural Replace tab. Set File type to Java.
Step 2 — Define search template
Paste this into the Search template field:
oldMethod($arg$)Click Edit variables... next to $arg$, check This variable is a... and select Expression. Add a text constraint: instanceof java.lang.String. This ensures we only match string arguments.
Step 3 — Define replacement template
In the Replace template field:
newMethod($arg$, Locale.getDefault())The variable $arg$ is reused automatically — its matched expression is inserted verbatim.
Step 4 — Preview and apply
Click Find. IntelliJ shows a tool window with every match, grouped by file. Inspect a few entries: the preview pane displays the exact before/after diff. When satisfied, click Replace All. The operation is recorded as a single Structural Replace entry in Local History.
Advanced constraints and team sharing
SSR templates accept richer constraints than the UI suggests. You can edit the underlying XML directly (right-click a template → Copy as XML) to express:
$arg$ instanceof List<?>— matches any generic list.$receiver$ @Nullable— matches only annotated receivers.script: $arg$.getText().contains("test")— Groovy script filter for complex logic.
Exported XML files live in .idea/structuralSearch/ or can be shared via File → Manage IDE Settings → Export Settings. A teammate imports the XML through the SSR dialog's Import Template button, getting identical behavior without manual setup.
Trade-offs: when SSR slows down or misses matches
The research brief flags two practical limits:
- PSI parsing edge cases: Complex generics like
Map<String, List<? extends Number>>or heavy annotation processors (Lombok, MapStruct) can cause the PSI to differ from source text. SSR may produce false positives (matching a synthetic method) or false negatives (skipping a generated accessor). - Index latency on huge projects: A 500-module monorepo can take 10–30 seconds to populate the match list. Mitigation: narrow the Scope dropdown to a single module or a custom scope before clicking Find.
Verification strategy from the brief: run the template on a fresh IntelliJ 2026.3 instance with a multi-module Java project, confirm all intended occurrences highlight, and ensure Replace All completes without errors. Then export the template, import it into another IDE instance, and execute on a sample file to validate portability.
Try it on a bounded scope first
If you're facing a signature migration, start with SSR on one module. Define the search template with an instanceof constraint, preview the diffs, and apply. Export the XML so the rest of the team can replay the same transformation identically. For the remaining modules, either run the saved template or script the replacement via IntelliJ's headless CLI (idea.sh structural-replace) in CI — but that's a topic for another post.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.