spring-projects/spring-security · error · IllegalStateException

Cannot apply {configurer} to already built object

Error message

Cannot apply {configurer} to already built object

What it means

AbstractConfiguredSecurityBuilder.add registers a SecurityConfigurer, but once the builder's buildState is 'configured' (or beyond), no new configurers may be added, so it throws IllegalStateException. This protects the invariant that configuration happens strictly before doBuild() executes the configurers.

Source

Thrown at config/src/main/java/org/springframework/security/config/annotation/AbstractConfiguredSecurityBuilder.java:209

	 * @return the shared Objects
	 */
	public Map<Class<?>, Object> getSharedObjects() {
		return Collections.unmodifiableMap(this.sharedObjects);
	}

	/**
	 * Adds {@link SecurityConfigurer} ensuring that it is allowed and invoking
	 * {@link SecurityConfigurer#init(SecurityBuilder)} immediately if necessary.
	 * @param configurer the {@link SecurityConfigurer} to add
	 */
	@SuppressWarnings("unchecked")
	private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
		Assert.notNull(configurer, "configurer cannot be null");
		Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
			.getClass();
		synchronized (this.configurers) {
			if (this.buildState.isConfigured()) {
				throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
			}
			List<SecurityConfigurer<O, B>> configs = null;
			if (this.allowConfigurersOfSameType) {
				configs = this.configurers.get(clazz);
			}
			configs = (configs != null) ? configs : new ArrayList<>(1);
			configs.add(configurer);
			this.configurers.put(clazz, configs);
			if (this.buildState.isInitializing()) {
				this.configurersAddedInInitializing.add(configurer);
			}
		}
	}

	/**
	 * Gets all the {@link SecurityConfigurer} instances by its class name or an empty
	 * List if not found. Note that object hierarchies are not considered.
	 * @param clazz the {@link SecurityConfigurer} class to look for

View on GitHub (pinned to 96852e8860)

Solutions

  1. Move all .apply()/.with() calls before the build() invocation in your configuration code.
  2. Create a fresh builder instance instead of reusing a built one; builders are single-use.
  3. Guard conditional configuration so it executes inside the same configuration method, before building, e.g. apply inside the SecurityFilterChain bean's lambda.
  4. If you need post-built access, use builder.getObject() rather than reconfiguring.

Example fix

// before
http.csrf(); http.build(); http.apply(new MyConfigurer()); // IllegalStateException
// after
http.csrf().apply(new MyConfigurer());
SecurityFilterChain chain = http.build();
Defensive patterns

Strategy: type-guard

Validate before calling

if (builder instanceof AbstractConfiguredSecurityBuilder<?,?> b && b.isBuilt()) {
    throw new IllegalStateException("builder already built; create a new one");
}

Try / catch

try {
    builder.apply(configurer);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("already built")) {
        builder = createFreshBuilder();
        builder.apply(configurer);
    }
}

Prevention

When it happens

Trigger: Calling .apply(configurer) or .with(...) on a builder after build() (or after configuration was finalized), e.g. adding HttpSecurity configurers after the filter chain was built, or reusing a single builder instance across two builds.

Common situations: Storing an HttpSecurity/WebSecurityCustomizer in a field and configuring it lazily after startup; calling build() twice on the same builder then applying more configurers; framework callbacks (e.g. BeanPostProcessor) touching the builder post-build.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/cdc7d63dcbc40bb9. Report an issue: GitHub.