Skip to content

Make localeResolver overridable via @ConditionalOnMissingBean#15751

Closed
codeconsole wants to merge 1 commit into
apache:8.0.xfrom
codeconsole:fix/locale-resolver-conditional-bean
Closed

Make localeResolver overridable via @ConditionalOnMissingBean#15751
codeconsole wants to merge 1 commit into
apache:8.0.xfrom
codeconsole:fix/locale-resolver-conditional-bean

Conversation

@codeconsole

@codeconsole codeconsole commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

What

I18nAutoConfiguration.localeResolver() registers the localeResolver bean unconditionally. As a result, an application-supplied LocaleResolver overrides the framework default — a bean-definition override warning on startup by default, and a hard startup failure when spring.main.allow-bean-definition-overriding=false.

This adds @ConditionalOnMissingBean(name = "localeResolver") so the Grails default backs off cleanly when the application already defines one. This mirrors Spring Boot's own WebMvcAutoConfiguration.localeResolver(), which is @ConditionalOnMissingBean for exactly this reason — registering it unconditionally is the divergence.

@Bean(DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
@ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
public LocaleResolver localeResolver() {
    return new SessionLocaleResolver();
}

Scope — what this covers

@ConditionalOnMissingBean is evaluated during auto-configuration, so it lets the Grails default back off in favor of a localeResolver bean that is already present at that point:

  • an application @Bean localeResolver (user @Configuration is processed before auto-configuration),
  • an @Imported configuration, or
  • another auto-configuration ordered @AutoConfigureBefore grails-i18n.

It does not affect beans contributed after auto-configuration — e.g. a Grails plugin's doWithSpring, which is registered later and still overrides via the normal override mechanism. Silencing that case additionally requires the contributor to register its resolver during auto-config (e.g. an @AutoConfigureBefore(I18nAutoConfiguration) class) so this condition can see it and back off. This PR is the upstream half that makes such a clean backoff possible.

Behavior for a plain app (no competing localeResolver) is unchanged: the condition passes and the Grails default registers as before.

Why keyed on the name, not the type

DispatcherServlet resolves the locale resolver by the fixed bean name DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME ("localeResolver"). A LocaleResolver bean registered under a different name is never the one actually used, so keying the condition on the bean name matches the real lookup semantics — a by-type condition could back off for an unrelated resolver bean that DispatcherServlet ignores.

Note

The sibling beans in the same class (localeChangeInterceptor, messageSource) are also registered unconditionally; this PR is intentionally scoped to localeResolver only. Happy to extend if maintainers prefer.

I18nAutoConfiguration registered the `localeResolver` bean
unconditionally. As a result, any application- or plugin-supplied
LocaleResolver produced a bean-definition override of the framework
default - a warning by default, and a hard startup failure when
spring.main.allow-bean-definition-overriding=false.

Spring Boot's own WebMvcAutoConfiguration.localeResolver() is annotated
@ConditionalOnMissingBean for exactly this reason. Apply the same so the
Grails default backs off cleanly when a localeResolver bean is already
present.

The condition is keyed on the bean name rather than the type:
DispatcherServlet resolves the locale resolver by the fixed
DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME ("localeResolver"), so a
LocaleResolver bean registered under a different name would not be the
one actually used.
@codeconsole codeconsole marked this pull request as draft June 22, 2026 02:08
@testlens-app

testlens-app Bot commented Jun 22, 2026

Copy link
Copy Markdown

🚨 TestLens detected 5 failed tests 🚨

Here is what you can do:

  1. Inspect the test failures carefully.
  2. If you are convinced that some of the tests are flaky, you can mute them below.
  3. Finally, trigger a rerun by checking the rerun checkbox.

Test Summary

CI - Groovy Joint Validation Build / build_grails > :grails-test-examples-app1:integrationTest

Test Runs
BookFunctionalSpec > Test that switching language results in correct encodings

CI - Groovy Joint Validation Build / build_grails > :grails-test-examples-scaffolding:integrationTest

Test Runs
UserControllerSpec > User list

CI / Functional Tests (Java 21, indy=false) > :grails-test-examples-app1:integrationTest

Test Runs
BookFunctionalSpec > Test that switching language results in correct encodings

CI / Functional Tests (Java 21, indy=true) > :grails-test-examples-app1:integrationTest

Test Runs
BookFunctionalSpec > Test that switching language results in correct encodings

CI / Functional Tests (Java 25, indy=false) > :grails-test-examples-app1:integrationTest

Test Runs
BookFunctionalSpec > Test that switching language results in correct encodings

🏷️ Commit: dbee13e
▶️ Tests: 42030 executed
⚪️ Checks: 44/44 completed

Test Failures

BookFunctionalSpec > Test that switching language results in correct encodings (:grails-test-examples-app1:integrationTest in CI / Functional Tests (Java 21, indy=false))
Condition not satisfied:

page.createButton.text() == 'Book anlegen'
|    |            |      |
|    |            |      false
|    |            |      11 differences (8% similarity)
|    |            |      (New-) (Book---)
|    |            |      (Book) (anlegen)
|    |            New Book
|    functionaltests.pages.BookShowPage -> createButton: geb.navigator.DefaultNavigator
functionaltests.pages.BookShowPage

	at functionaltests.BookFunctionalSpec.Test that switching language results in correct encodings(BookFunctionalSpec.groovy:48)
expected actual
Book anlegen New Book
BookFunctionalSpec > Test that switching language results in correct encodings (:grails-test-examples-app1:integrationTest in CI / Functional Tests (Java 21, indy=true))
Condition not satisfied:

page.createButton.text() == 'Book anlegen'
|    |            |      |
|    |            |      false
|    |            |      11 differences (8% similarity)
|    |            |      (New-) (Book---)
|    |            |      (Book) (anlegen)
|    |            New Book
|    functionaltests.pages.BookShowPage -> createButton: geb.navigator.DefaultNavigator
functionaltests.pages.BookShowPage

	at functionaltests.BookFunctionalSpec.Test that switching language results in correct encodings(BookFunctionalSpec.groovy:48)
expected actual
Book anlegen New Book
BookFunctionalSpec > Test that switching language results in correct encodings (:grails-test-examples-app1:integrationTest in CI / Functional Tests (Java 25, indy=false))
Condition not satisfied:

page.createButton.text() == 'Book anlegen'
|    |            |      |
|    |            |      false
|    |            |      11 differences (8% similarity)
|    |            |      (New-) (Book---)
|    |            |      (Book) (anlegen)
|    |            New Book
|    functionaltests.pages.BookShowPage -> createButton: geb.navigator.DefaultNavigator
functionaltests.pages.BookShowPage

	at functionaltests.BookFunctionalSpec.Test that switching language results in correct encodings(BookFunctionalSpec.groovy:48)
expected actual
Book anlegen New Book
BookFunctionalSpec > Test that switching language results in correct encodings (:grails-test-examples-app1:integrationTest in CI - Groovy Joint Validation Build / build_grails)
Condition not satisfied:

page.createButton.text() == 'Book anlegen'
|    |            |      |
|    |            |      false
|    |            |      11 differences (8% similarity)
|    |            |      (New-) (Book---)
|    |            |      (Book) (anlegen)
|    |            New Book
|    functionaltests.pages.BookShowPage -> createButton: geb.navigator.DefaultNavigator
functionaltests.pages.BookShowPage

	at functionaltests.BookFunctionalSpec.Test that switching language results in correct encodings(BookFunctionalSpec.groovy:48)
expected actual
Book anlegen New Book
UserControllerSpec > User list (:grails-test-examples-scaffolding:integrationTest in CI - Groovy Joint Validation Build / build_grails)
geb.waiting.WaitTimeoutException: condition did not pass in 30 seconds (failed with exception)
	at geb.waiting.Wait.waitFor(Wait.groovy:128)
	at geb.waiting.DefaultWaitingSupport.doWaitFor(DefaultWaitingSupport.groovy:55)
	at geb.waiting.DefaultWaitingSupport.waitFor(DefaultWaitingSupport.groovy:45)
	at geb.Page.waitFor(Page.groovy:120)
	at com.example.pages.LoginPage.login(LoginPage.groovy:39)
	at com.example.UserControllerSpec.setup(UserControllerSpec.groovy:33)
Caused by: Assertion failed: 

title != pageTitle
|     |  |
|     |  'Please sign in'
|     false
'Please sign in'

	at com.example.pages.LoginPage.login_closure1(LoginPage.groovy:39)
	at com.example.pages.LoginPage.login_closure1(LoginPage.groovy)
	at geb.waiting.Wait.waitFor(Wait.groovy:117)
	... 5 more

Muted Tests

Select tests to mute in this pull request:

  • BookFunctionalSpec > Test that switching language results in correct encodings
  • UserControllerSpec > User list

Reuse successful test results:

  • ♻️ Only rerun the tests that failed or were muted before

Click the checkbox to trigger a rerun:

  • Rerun jobs

Learn more about TestLens at testlens.app.

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.

1 participant