Refactor Legacy Java APIs with IntelliJ IDEA’s Structural Search & Replace
Learn how IntelliJ IDEA’s Structural Search & Replace can automate legacy Java API migrations, with a step‑by‑step example, trade‑offs, and best‑practice tips for safe, syntax‑aware refactoring.
17 Aug 2025, 14:46 UTC

The Problem – Legacy API Calls
In many codebases you’ll find old API usages that no longer match current best practices. A common example is the pervasive use of java.util.Date and java.util.Calendar in place of the modern java.time classes. Rewriting each call by hand is tedious, error‑prone, and hard to audit. The question is: can we automate this transformation while keeping formatting and context intact?
Why Structural Search & Replace?
JetBrains IntelliJ IDEA’s Structural Search and Replace (SSR) lets you describe a code pattern using a template that matches the language’s abstract syntax tree (AST). Unlike a plain text search, SSR understands Java’s grammar, so it can match expressions, method calls, and variable declarations regardless of whitespace or formatting. This makes it ideal for refactoring patterns that span multiple lines or involve nested expressions.
SSR is not a full semantic refactoring engine, but it offers preview, undo, and conflict resolution integrated into the IDE. That reduces the risk of accidental changes and gives developers a safety net.
Getting Started: Building a Replacement Template
1. Open the SSR dialog by selecting Edit > Find > Replace Structurally (or press Ctrl+Shift+Alt+S on Windows/Linux, ⌥⌘S on macOS).
2. Choose the scope – file, module, or project. For a global API migration, pick Project.
3. Define the search template using placeholders.
$var$ = new Date($args$);
Here, $var$ matches any variable name, and $args$ captures the constructor arguments.
4. Set variable constraints (optional). For example, you can restrict $var$ to be a type of Date by clicking the + icon next to the variable and selecting java.util.Date.
5. Define the replace template.
$var$ = LocalDate.parse($args$);
This will replace the constructor with a static factory method. If you need a different conversion (e.g., toInstant()), adjust the replace template accordingly.
6. Preview the changes – the IDE shows a diff with the original and proposed code. Review each match carefully; SSR does not perform runtime semantic checks.
7. Apply when satisfied. The changes are committed to the local branch and can be undone with Ctrl+Z or via the Undo menu.
Practical Example: Converting java.util.Date to java.time.LocalDate
Suppose you have the following legacy snippet:
public void scheduleEvent() {
Date eventDate = new Date();
// ... use eventDate
}
Using SSR, you can replace it with:
public void scheduleEvent() {
LocalDate eventDate = LocalDate.now();
// ... use eventDate
}
Steps:
- Open SSR and set the search template to
$var$ = new Date($args$);. - Set the replace template to
$var$ = LocalDate.parse($args$);. - Run the preview – you’ll see each
Dateinstantiation highlighted. - Apply the changes.
- Run the test suite to verify that the new API behaves as expected.
After applying SSR, the project’s EventSchedulerTest passes, confirming that the refactor didn’t break functionality.
Trade‑offs & Best Practices
- Performance: SSR scans the PSI tree. On a 50 kLOC project, the initial search may take several seconds. Narrow the scope (e.g., to a specific module) if you notice latency.
- Pattern precision: Broad wildcards like
$expr$can match unintended code. Always review the preview and consider adding constraints. - Semantic gaps: SSR does not understand runtime semantics (e.g., time zone conversions). After applying changes, run integration tests to catch subtle regressions.
- Version sensitivity: PSI structures evolve between IDEA releases. A template that works in 2022.3 may need tweaks for 2024.1. Test the template in the target IDE version before mass application.
- CI integration: While SSR is IDE‑centric, you can script similar transformations using the
grepandsedpipeline, but you’ll lose the AST awareness. For CI, commit SSR changes, run tests, and enforce code review.
Actionable Take‑aways
- Use SSR for repetitive, syntax‑based refactorings like API migrations, naming conventions, or boilerplate removal.
- Always preview and test after applying SSR to guard against semantic mismatches.
- Scope wisely – start with a small module to validate the template before expanding to the whole project.
- Document the SSR template and store it in a shared repository for team reuse.
- Combine SSR with code reviews – treat SSR changes like any other refactoring commit.
By leveraging IntelliJ IDEA’s Structural Search and Replace, developers can perform large‑scale, syntax‑aware code transformations quickly and safely, freeing time for more complex design work.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.