We allow case insensitive enum values in the Environment but @ConditionalOnProperty does not really support that.

There is already one usage in the code base, see TestDatabaseAutoConfiguration.

See also #6083

Comment From: snicoll

Turns out we can implement #6083 in a different way. TestDatabaseAutoConfiguration uses properties mapping so the case will be right.

Comment From: quaff

Please reconsider, I think it's very common, take Spring AI's ServerProtocol for example, spring.ai.mcp.server.protocol=STATELESS works but spring.ai.mcp.server.protocol=stateless will not works, see:

https://github.com/spring-projects/spring-ai/blob/6880753dd607532c9f542d5ce6fa0c1dbd0ff68e/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerStatelessAutoConfiguration.java#L237

https://github.com/spring-projects/spring-ai/blob/6880753dd607532c9f542d5ce6fa0c1dbd0ff68e/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/properties/McpServerProperties.java#L120

Comment From: wilkinsona

That looks like a ~bug in Spring AI to me. I'd recommend that the condition uses stateless rather than STATELESS.

We can't easily use relaxed binding with @ConditionalOnProperty as it knows nothing about the type that the property is bound to. It therefore cannot detect that it's an enum. Applying case-insensitive checks in all cases has the potential to be a breaking change.

An alternative would be to introduce ConditionalOnEnumProperty that could then perform case-insensitive matching. Given that we have ConditionalOnBooleanProperty now, that's perhaps not such a wild idea. Something like this:

/*
 * Copyright 2012-present the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.condition;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Conditional;
import org.springframework.core.env.Environment;

/**
 * {@link Conditional @Conditional} that checks if the specified properties have a
 * specific enum value. By default the properties must be present in the
 * {@link Environment}. They much be equal, using relaxed matching, to the configured
 * value. The {@link #havingValue()} and {@link #matchIfMissing()} attributes allow
 * further customizations.
 * <p>
 * If the property is not contained in the {@link Environment} at all, the
 * {@link #matchIfMissing()} attribute is consulted. By default missing attributes do not
 * match.
 *
 * @author Andy Wilkinson
 * @since TBD
 * @see ConditionalOnProperty
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnEnumProperty {

    /**
     * Alias for {@link #name()}.
     * @return the names
     */
    String[] value() default {};

    /**
     * A prefix that should be applied to each property. The prefix automatically ends
     * with a dot if not specified. A valid prefix is defined by one or more words
     * separated with dots (e.g. {@code "acme.system.feature"}).
     * @return the prefix
     */
    String prefix() default "";

    /**
     * The name of the properties to test. If a prefix has been defined, it is applied to
     * compute the full key of each property. For instance if the prefix is
     * {@code app.config} and one value is {@code my-value}, the full key would be
     * {@code app.config.my-value}
     * <p>
     * Use the dashed notation to specify each property, that is all lower case with a "-"
     * to separate words (e.g. {@code my-long-property}).
     * <p>
     * If multiple names are specified, all of the properties have to pass the test for
     * the condition to match.
     * @return the names
     */
    String[] name() default {};

    /**
     * The type of the enum.
     * @return the enum type
     */
    Class<? extends Enum<?>> enumType();

    /**
     * The expected value for the properties.
     * @return the expected value
     */
    String havingValue();

    /**
     * Specify if the condition should match if the property is not set. Defaults to
     * {@code false}.
     * @return if the condition should match if the property is missing
     */
    boolean matchIfMissing() default false;

}

Matching would be the same as LenientStringToEnumConverterFactory. It would also be technically possible to fail at compile time if havingValue didn't match one of the enum's values.

I'm not sure this is a good idea for a problem that doesn't appear to affect many people. We'll discuss it.

Comment From: quaff

Or introduce boolean ignoreCase() default false; for @ConditionalOnProperty, it's not specific to enum.

Comment From: philwebb

There's more to enum matching that just ignoring case. For example, MyEnum.FOO_BAR will work with a property value of foobar.

Comment From: philwebb

One other option to consider is adding a type attribute to @ConditionalProperty that we could use to convert the string values.

e.g.:

@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "protocol", havingValue = "stateless",
                matchIfMissing = false, type=ServerProtocol.class)

We might even be able to get the type from the config metadata (although that feels a bit brittle).

I think I lean towards @ConditionalOnEnumProperty, but we probably need some more discussion on this before we do anything.