Skip to content

Rethrow Error from @PostConstruct method instead of wrapping it in RuntimeException#3039

Open
vasiliy-mikhailov wants to merge 1 commit into
google:mainfrom
vasiliy-mikhailov:fix/postconstruct-error-propagation
Open

Rethrow Error from @PostConstruct method instead of wrapping it in RuntimeException#3039
vasiliy-mikhailov wants to merge 1 commit into
google:mainfrom
vasiliy-mikhailov:fix/postconstruct-error-propagation

Conversation

@vasiliy-mikhailov

Copy link
Copy Markdown
Contributor

PostConstructAdapterFactory rethrows a RuntimeException cause as-is, but wraps everything else (throw new RuntimeException(e.getCause())). An Error thrown by the @PostConstruct method — AssertionError, OutOfMemoryError, NoClassDefFoundError, … — is unchecked like RuntimeException and should propagate unchanged, not be wrapped in a RuntimeException (where a catch (RuntimeException) could swallow what is meant to be unrecoverable).

This adds the symmetric else if (e.getCause() instanceof Error) throw (Error) e.getCause();, matching how the same block already treats RuntimeException. The new RuntimeException(...) fallback stays for genuinely checked causes. Includes a regression test.

@Marcono1234 Marcono1234 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks! I have added two suggestions.

The direct project members might have additional comments though.

new GsonBuilder().registerTypeAdapterFactory(new PostConstructAdapterFactory()).create();

Throwable thrown =
org.junit.Assert.assertThrows(

@Marcono1234 Marcono1234 Jun 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Test compilation fails because this should use an import for org.junit.Assert instead of the qualified name, respectively a static import.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — switched to the static import so assertThrows is used directly. The qualified org.junit.Assert.assertThrows(...) left the import static unused, which the build rejected. Thanks!

Comment on lines +108 to +109
assertThat(thrown).isInstanceOf(Error.class);
assertThat(thrown).isNotInstanceOf(RuntimeException.class);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could make this more specific to check for the exact custom error:

Suggested change
assertThat(thrown).isInstanceOf(Error.class);
assertThat(thrown).isNotInstanceOf(RuntimeException.class);
assertThat(thrown).isInstanceOf(AssertionError.class);
assertThat(thrown).hasMessageThat().isEqualTo("error from post-construct");

(Or maybe even change assertThrows(Throwable.class, ...) to AssertionError error = assertThrows(AssertionError.class, ...)?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — switched to assertThrows(AssertionError.class, ...), so it now also asserts the propagated throwable is an AssertionError rather than a wrapping RuntimeException.

One wrinkle on the exact message: Gson.fromJson re-tags AssertionErrors with an "AssertionError (GSON <version>): " prefix (keeping the original as the cause), so isEqualTo("error from post-construct") does not hold on the top-level message. I used hasMessageThat().contains("error from post-construct") to keep the specificity while staying robust to the version tag.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One wrinkle on the exact message: Gson.fromJson re-tags AssertionErrors with an "AssertionError (GSON <version>): " prefix

Ah, maybe it would be better to use a different Error subclass then (or directly Error), possibly even a custom subclass of Error (to no cause confusion when using any of the existing Error subclasses, e.g. throwing an error such as StackOverflowError might be confusing).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — switched to a dedicated CustomTestError extends Error thrown from @PostConstruct, so there is no AssertionError re-tagging and no overlap with a meaningful JVM error such as StackOverflowError. The message assertion is now exact (isEqualTo("error from post-construct")).

@vasiliy-mikhailov vasiliy-mikhailov force-pushed the fix/postconstruct-error-propagation branch from 3bf9f7a to 3d53dc5 Compare June 22, 2026 17:00
…ntimeException

PostConstructAdapterFactory rethrows a RuntimeException cause as-is but wraps
everything else. An Error thrown by the @PostConstruct method (AssertionError,
OutOfMemoryError, ...) is unchecked like RuntimeException and should propagate
as-is, not be smothered in a RuntimeException. Rethrow Error the same way.
Adds a regression test.
@vasiliy-mikhailov vasiliy-mikhailov force-pushed the fix/postconstruct-error-propagation branch from 3d53dc5 to 7648fad Compare June 22, 2026 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants