Skip to content

Preserve throws clause when checked exceptions exist outside assertThrows.#899

Merged
timtebeek merged 2 commits intoopenrewrite:mainfrom
motlin:expected-exception-preserve-throws
Feb 21, 2026
Merged

Preserve throws clause when checked exceptions exist outside assertThrows.#899
timtebeek merged 2 commits intoopenrewrite:mainfrom
motlin:expected-exception-preserve-throws

Conversation

@motlin
Copy link
Contributor

@motlin motlin commented Jan 28, 2026

What's changed?

The ExpectedExceptionToAssertThrows recipe now preserves the throws clause when statements before the thrown.expect() call throw checked exceptions.

Previously, the recipe unconditionally removed the throws clause from test methods when converting ExpectedException to assertThrows(). This caused compilation errors when code outside the assertThrows() lambda still threw checked exceptions.

Key changes:

  • Added findPredecessorStatements() to track statements before the first expect*() call
  • Added statementsBeforeExpectThrowCheckedException() to detect if those statements throw checked exceptions
  • Added isCheckedException() to distinguish checked exceptions from RuntimeException/Error
  • Modified visitMethodDeclaration() to preserve throws when pre-expect code throws checked exceptions

What's your motivation?

When migrating tests that have setup code before thrown.expect(), the recipe was incorrectly removing the throws declaration, causing compilation failures.

Example:

@Test
public void testMethod() throws InterruptedException {
    setup();  // throws InterruptedException - NOT wrapped in assertThrows
    this.thrown.expect(IllegalArgumentException.class);
    doSomething();
}

The recipe was removing throws InterruptedException even though setup() is not wrapped in the assertThrows() lambda.

Anything in particular you'd like reviewers to focus on?

The logic for detecting checked exceptions in statementThrowsCheckedException() - it recursively visits method invocations and constructor calls to check their thrown exception types. Please verify this covers all cases.

Anyone you would like to review specifically?

Have you considered any alternatives or workarounds?

Any additional context

The fix uses JavaType.Method.getThrownExceptions() to inspect method types, which requires proper type attribution. This should work in typical recipe execution contexts.

Checklist

  • I've added unit tests to cover both positive and negative cases
  • I've read and applied the recipe conventions and best practices
  • I've used the IntelliJ IDEA auto-formatter on affected files

@github-project-automation github-project-automation bot moved this to In Progress in OpenRewrite Jan 28, 2026
@timtebeek timtebeek self-requested a review January 29, 2026 00:54
@motlin motlin force-pushed the expected-exception-preserve-throws branch 3 times, most recently from fd06b9c to e63c8f1 Compare February 3, 2026 14:57
@motlin motlin force-pushed the expected-exception-preserve-throws branch from e63c8f1 to c5d641e Compare February 5, 2026 18:44
@motlin
Copy link
Contributor Author

motlin commented Feb 20, 2026

@timtebeek I wanted to check what you think of this one. I've been using it in a SNAPSHOT build against parts of my codebase and it's working well for me.

@motlin motlin force-pushed the expected-exception-preserve-throws branch from c5d641e to 877cc37 Compare February 20, 2026 16:24
@timtebeek
Copy link
Member

Glad to hear that's working well for you already! I'm admittedly behind on reviews, but that comes with landing Kotlin v2 and Python in the same week, as well as three online trainings. 😅 It's at the top of my list now, but first I'm out climbing. 🧗🏻

Copy link
Member

@timtebeek timtebeek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot! Great to not have compilation issues when other exceptions are declared thrown.

@github-project-automation github-project-automation bot moved this from In Progress to Ready to Review in OpenRewrite Feb 21, 2026
@timtebeek timtebeek merged commit 36ebbba into openrewrite:main Feb 21, 2026
1 check passed
@github-project-automation github-project-automation bot moved this from Ready to Review to Done in OpenRewrite Feb 21, 2026
mergify bot added a commit to robfrank/linklift that referenced this pull request Feb 26, 2026
…rom 3.27.0 to 3.28.0 [skip ci]

Bumps [org.openrewrite.recipe:rewrite-testing-frameworks](https://github.com/openrewrite/rewrite-testing-frameworks) from 3.27.0 to 3.28.0.
Release notes

*Sourced from [org.openrewrite.recipe:rewrite-testing-frameworks's releases](https://github.com/openrewrite/rewrite-testing-frameworks/releases).*

> 3.28.0
> ------
>
> What's Changed
> --------------
>
> * Fix 6 issues in AssertJ recipe migrations by [`@​timtebeek`](https://github.com/timtebeek) in [openrewrite/rewrite-testing-frameworks#911](https://redirect.github.com/openrewrite/rewrite-testing-frameworks/pull/911)
> * Preserve throws clause when checked exceptions exist outside assertThrows. by [`@​motlin`](https://github.com/motlin) in [openrewrite/rewrite-testing-frameworks#899](https://redirect.github.com/openrewrite/rewrite-testing-frameworks/pull/899)
> * Pin AssertJ to stable 3.27.7 by [`@​timtebeek`](https://github.com/timtebeek) in [openrewrite/rewrite-testing-frameworks#913](https://redirect.github.com/openrewrite/rewrite-testing-frameworks/pull/913)
> * Add .contextSensitive() to Hamcrest conversion templates and improve type matching in `CollapseConsecutiveAssertThatStatements` by [`@​timtebeek`](https://github.com/timtebeek) in [openrewrite/rewrite-testing-frameworks#912](https://redirect.github.com/openrewrite/rewrite-testing-frameworks/pull/912)
> * Fix 2 AssertJ migration recipe issues found via large-scale validation by [`@​timtebeek`](https://github.com/timtebeek) in [openrewrite/rewrite-testing-frameworks#914](https://redirect.github.com/openrewrite/rewrite-testing-frameworks/pull/914)
> * Add Hamcrest to AssertJ recipes for hasProperty, everyItem, and hasItem(Matcher) by [`@​timtebeek`](https://github.com/timtebeek) in [openrewrite/rewrite-testing-frameworks#916](https://redirect.github.com/openrewrite/rewrite-testing-frameworks/pull/916)
> * Add missing Hamcrest not(Matcher) to AssertJ conversions by [`@​timtebeek`](https://github.com/timtebeek) in [openrewrite/rewrite-testing-frameworks#917](https://redirect.github.com/openrewrite/rewrite-testing-frameworks/pull/917)
> * Do not incorrectly strip method calls from assertion arguments by [`@​timtebeek`](https://github.com/timtebeek) in [openrewrite/rewrite-testing-frameworks#918](https://redirect.github.com/openrewrite/rewrite-testing-frameworks/pull/918)
>
> **Full Changelog**: <openrewrite/rewrite-testing-frameworks@v3.27.0...v3.28.0>


Commits

* [`c2860b4`](openrewrite/rewrite-testing-frameworks@c2860b4) OpenRewrite recipe best practices
* [`c75ba77`](openrewrite/rewrite-testing-frameworks@c75ba77) Do not incorrectly strip method calls from assertion arguments ([#918](https://redirect.github.com/openrewrite/rewrite-testing-frameworks/issues/918))
* [`bf27539`](openrewrite/rewrite-testing-frameworks@bf27539) Add missing Hamcrest `not(Matcher)` to AssertJ conversions ([#866](https://redirect.github.com/openrewrite/rewrite-testing-frameworks/issues/866)) ([#917](https://redirect.github.com/openrewrite/rewrite-testing-frameworks/issues/917))
* [`4f3f340`](openrewrite/rewrite-testing-frameworks@4f3f340) Update Gradle wrapper to 9.3.1
* [`a850e44`](openrewrite/rewrite-testing-frameworks@a850e44) Add Hamcrest to AssertJ recipes for hasProperty, everyItem, and hasItem(Match...
* [`16f1501`](openrewrite/rewrite-testing-frameworks@16f1501) Fix 2 AssertJ migration recipe issues found via large-scale validation ([#914](https://redirect.github.com/openrewrite/rewrite-testing-frameworks/issues/914))
* [`c270c74`](openrewrite/rewrite-testing-frameworks@c270c74) Add .contextSensitive() to Hamcrest conversion templates and improve type mat...
* [`00bc57f`](openrewrite/rewrite-testing-frameworks@00bc57f) Pin AssertJ to stable 3.27.7 ([#913](https://redirect.github.com/openrewrite/rewrite-testing-frameworks/issues/913))
* [`36ebbba`](openrewrite/rewrite-testing-frameworks@36ebbba) Preserve throws clause when checked exceptions exist outside assertThrows. (#...
* [`58d2238`](openrewrite/rewrite-testing-frameworks@58d2238) OpenRewrite recipe best practices
* Additional commits viewable in [compare view](openrewrite/rewrite-testing-frameworks@v3.27.0...v3.28.0)
  
[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility\_score?dependency-name=org.openrewrite.recipe:rewrite-testing-frameworks&package-manager=maven&previous-version=3.27.0&new-version=3.28.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
  
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot show  ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants