App configuration support

This article describes the Spring Cloud Azure App Configuration library. This library loads configurations and feature flags from the Azure App Configuration service. The library generates PropertySource abstractions to match the abstractions already generated by the Spring environment, such as environment variables, command-line configurations, local configuration files, and so on.

Spring is an open-source application framework developed by VMware that provides a simplified, modular approach for creating Java applications. Spring Cloud Azure is an open-source project that provides seamless Spring integration with Azure services.

Prerequisites

Set up your App Configuration store

Use the following command to create your Azure App Configuration store:

az appconfig create \
    --resource-group <your-resource-group> \
    --name <name-of-your-new-store> \
    --sku Standard

This command creates a new, empty configuration store. You can upload your configurations by using the following import command:

az appconfig kv import \
    --name <name-of-your-new-store> \
    --source file \
    --path <location-of-your-properties-file> \
    --format properties \
    --prefix /application/

Confirm your configurations before loading them. You can upload YAML files by changing the format to YAML. The prefix field is important because it's the default prefix loaded by the client library.

Library usage

To use the feature in an application, you can build it as a Spring Boot application. The most convenient way to add the dependency is with the Spring Boot starter com.azure.spring:spring-cloud-azure-starter-appconfiguration-config. The following example pom.xml file uses Azure App Configuration:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>{spring-boot-version}</version>
    <relativePath />
</parent>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.azure.spring</groupId>
      <artifactId>spring-cloud-azure-dependencies</artifactId>
      <version>5.12.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.azure.spring</groupId>
        <artifactId>spring-cloud-azure-starter-appconfiguration-config</artifactId>
    </dependency>
</dependencies>
<build>
    <plugins>
           <plugin>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-maven-plugin</artifactId>
           </plugin>
    </plugins>
</build>

Note

If you're using Spring Boot 2.x, be sure to set the spring-cloud-azure-dependencies version to 4.18.0. For more information about the version used for this BOM, see Which Version of Spring Cloud Azure Should I Use.

The following example shows a basic Spring Boot application using App Configuration:

@SpringBootApplication
@RestController
public class Application {

    @RequestMapping("/")
    public String home() {
        return "Hello World!";
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

For this example, the bootstrap.properties file contains the following line:

spring.cloud.azure.appconfiguration.stores[0].connection-string=${CONFIG_STORE_CONNECTION_STRING}

CONFIG_STORE_CONNECTION_STRING is an environment variable with the connection string to your Azure App Configuration Store. You can access your connection string by using the following command:

az appconfig credential list --name <name-of-your-store>

By default, if no configurations are set, the configurations starting with /application/ are loaded with a default label of (No Label) unless a Spring Profile is set, in which case the default label is your Spring Profile. Because the store is empty, no configurations are loaded, but the Azure App Configuration Property Source is still generated.

A property source named /application/https://<name-of-your-store>.azconfig.io/ is created containing the properties of that store. The label used in the request is appended to the end of the name. If no label is set, the character \0 is present as an empty space.

Loading configuration

The library supports the loading of one or multiple App Configuration stores. In the situation where a key is duplicated across multiple stores, loading all stores results in the highest priority stores configuration being loaded. The last one wins. This process is illustrated in the following example:

spring.cloud.azure.appconfiguration.stores[0].connection-string=[first-store-connection-string]
spring.cloud.azure.appconfiguration.stores[1].connection-string=[second-store-connection-string]

In the example, if both the first and second stores have the same configuration, the configuration in the second store has the highest priority, and the last one wins.

Note

You can use Azure App Configuration settings like any other Spring Configuration. For more information, see Core Features in the Spring Boot documentation or Quickstart: Create a Java Spring app with Azure App Configuration.

Selecting configurations

Configurations are loaded by their key and label. By default, the configurations that start with the key /application/ are loaded. The default label is ${spring.profiles.active}. If ${spring.profiles.active} isn't set, then configurations with the null label are loaded. The null label appears as (No Label) in the Azure portal.

You can configure the configurations that are loaded by selecting different key and label filters, as shown in the following example:

spring.cloud.azure.appconfiguration.stores[0].selects[0].key-filter=[my-key]
spring.cloud.azure.appconfiguration.stores[0].selects[0].label-filter=[my-label]

The key-filter property supports the following filters:

Key filter Effect
* Matches any key.
abc Matches a key named abc.
abc* Matches key names that start with abc.
abc,xyz Matches key names abc or xyz. Limited to five comma-separated values.

The label-filter property supports the following filters:

Label Description
* Matches any label, including \0.
\0 Matches null labels, which appear as (No Label) in the Azure portal.
1.0.0 Matches label 1.0.0 exactly.
1.0.* Matches labels that start with 1.0.*.
,1.0.0 Matches labels null and 1.0.0. Limited to five comma-separated values.

If you're using YAML with label filters, and you need to start with null, then the label filter needs to be surrounded by single quotes, as shown in the following example:

spring:
  cloud:
    azure:
      appconfiguration:
        stores:
        - selects:
          - label-filter: ',1.0.0'

Note

You can't combine * with , in filters. In that case, you need to use an additional select value.

Spring Profiles

By default, spring.profiles.active is set as the default label-filter for all selected configurations. You can override this functionality by using label-filter. You can use the Spring Profiles in the label-filter by using ${spring.profiles.active}, as shown in the following example:

spring.cloud.azure.appconfiguration.stores[0].selects[0].label-filter=,${spring.profiles.active}
spring.cloud.azure.appconfiguration.stores[0].selects[1].label-filter=${spring.profiles.active}_local

In the first label-filter, all configurations with the null label are loaded, followed by all configurations matching the Spring Profiles. Spring Profiles have priority over the null configurations, because they're at the end.

In the second label-filter, the string _local is appended to the end of the Spring Profiles, though only to the last Spring Profile.

Disabled stores

Using the configuration spring.cloud.azure.appconfiguration.enabled, you can disable loading for all configuration stores. With the spring.cloud.azure.appconfiguration.stores[0].enabled configuration, you can disable an individual store.

In addition to disabling stores, you can configure stores to be disabled if they fail to load. For this configuration, use spring.cloud.azure.appconfiguration.stores[0].fail-fast. When fail-fast is disabled by setting it to false, a RuntimeException results in the application store being disabled with no configurations from it being loaded. If a configuration store is disabled on startup, it isn't checked for changes upon refresh. Also, there's no attempt to load values from it if configurations are updated.

If an error resulting in a RuntimeException happens during a refresh check or while attempting to reload configurations, then the refresh attempt ends and is retried after the refresh-interval has passed.

Authentication

The library supports all forms of identity supported by the Azure Identity Library. You can do authentication through configuration for connection strings and managed identity.

Connection string

Authentication through connection string is the simplest form to set up. You can access a store's connection strings by using the following command:

az appconfig credential list --name <name-of-your-store>

You can then set the spring.cloud.azure.appconfiguration.stores[0].connection-string property to the connection string. We highly recommend setting the connection string in the local configuration file to a placeholder value that maps to an environment variable. This approach enables you to avoid adding the connection string to source control.

Spring Cloud Azure configuration

You can use Spring Cloud Azure configuration to configure the library. You can use the following properties to configure the library:

spring.cloud.azure.appconfiguration.stores[0].endpoint= <URI-of-your-configuration-store>

When only the endpoint is set, the client library uses the DefaultAzureCredential to authenticate. The DefaultAzureCredential uses the following methods to authenticate:

  • Environment credential
  • Managed Identity credential
  • Azure Developer CLI credential
  • IntelliJ credential
  • Azure CLI credential
  • Azure PowerShell credential

You need to assign an identity such as a system assigned identity to read configurations. You can create this assignment by using the following command:

az role assignment create \
    --role "App Configuration Data Reader" \
    --assignee <your-client-ID> \
    --scope /subscriptions/<your-subscription>/resourceGroups/<your-stores-resource-group>/providers/Microsoft.AppConfiguration/configurationStores/<name-of-your-configuration-store>

Note

You can define only one authentication method per endpoint: connection string, user assigned identity, or token credential. If you need to mix and match, you can use ConfigurationClientCustomizer to modify stores that use a different method.

Geo-replication

The library supports the geo-replication feature of Azure App Configuration. This feature enables you to replicate your data to other locations. This feature is useful for high availability and disaster recovery.

Each replica you create has a dedicated endpoint. If your application resides in multiple geolocations, you can update each deployment of your application in a location to connect to the replica closer to that location, which helps minimize the network latency between your application and App Configuration. Because each replica has its separate request quota, this setup also helps the scalability of your application while it grows to a multi-region distributed service.

The failover may occur if the library observes any of the following conditions:

  • Receives responses with service unavailable status code (HTTP 500 or above) from an endpoint.
  • Experiences network connectivity issues.
  • Requests are throttled (HTTP status code 429).

Creating a configuration store with geo-replication

To create a replica of your configuration store, you can use the Azure CLI or the Azure portal. The following example uses the Azure CLI to create a replica in the East US 2 region:

az appconfig replica create --location --name --store-name [--resource-group]

Using the configuration store replica

After you've created a replica, you can use it in your application. Like the origin store, you can connect to your replica using Microsoft Entra ID or a connection string.

To use Microsoft Entra ID to connect to your replica, you need to list the endpoints of your configuration store instances, as shown in the following example:

spring.cloud.azure.appconfiguration.stores[0].endpoints[0]=[your primary store endpoint]
spring.cloud.azure.appconfiguration.stores[0].endpoints[1]=[your replica store endpoint]

You can list as many endpoints as you have replicas. The library tries to connect to the endpoints in the order they're listed. If the library is unable to connect to a replica, it tries the next one in the list. After a period of time has passed, the library attempts to reconnect to the preferred endpoints.

Key values

Azure App Configuration supports multiple types of key values, some of which have special features built into them. Azure App Configuration has built-in support for the JSON content type, Spring placeholders, and Key Vault references.

Placeholders

The library supports configurations with ${}-style environment placeholders. When referencing an Azure App Configuration key with a placeholder, remove prefixes from the reference. For example, /application/config.message is referenced as ${config.message}.

Note

The prefix being removed matches the value spring.cloud.azure.appconfiguration.stores[0].selects[0].key-filter.

JSON

Configurations that have a content-type application/json are processed as JSON objects. This feature enables you to map one configuration to a complex object inside a @ConfigurationProperties. For example, consider the JSON key /application/config.colors with the following value:

{
 "Red": {
  "value": [255, 0, 0]
 },
 "Blue": {
  "value": [0, 255, 0]
 },
 "Green": {
  "value": [0, 0, 255]
 }
}

This key maps to the following code:

@ConfigurationProperties(prefix = "config")
public class MyConfigurations {

    private Map<String, Color> colors;

}

Key Vault references

Azure App Configuration and its libraries support referencing secrets stored in Key Vault. In App Configuration, you can create keys with values that map to secrets stored in a Key Vault. Secrets are securely stored in Key Vault, but can be accessed in the same way as any other configuration after it's loaded.

Your application uses the client provider to retrieve Key Vault references, just as it does for any other keys stored in App Configuration. Because the client recognizes the keys as Key Vault references, they have a unique content-type, and the client connects to Key Vault to retrieve their values for you.

Note

Key Vault only allows for secrets to be retrieved one at a time, so each Key Vault reference stored in App Configuration results in a pull against Key Vault.

Creating Key Vault references

You can create a Key Vault reference in the Azure portal by going to Configuration explorer > Create > Key Vault reference. You can then select a secret to reference from any of the Key Vaults you have access to. You can also create arbitrary Key Vault references from the Input tab. In the Azure portal, enter a valid URI.

You can also create a Key Vault reference through the Azure CLI by using the following command:

az appconfig kv set-keyvault \
    --name <name-of-your-store> \
    --key <key-name> \
    --secret-identifier <URI-to-your-secret>

You can create any secret-identifier through the Azure CLI. Secret identifiers just require the format {vault}/{collection}/{name}/{version?} where the version section is optional.

Using Key Vault references

You can use Spring Cloud Azure configuration to configure the library. You can use the same credential used to connect to App Configuration to connect to Azure Key Vault.

Resolve non-Key Vault secrets

The App Configuration library provides a method to locally resolve secrets that don't have a Key Vault associated with them. This resolution is done through the KeyVaultSecretProvider. The KeyVaultSecretProvider is called when a TokenCredential isn't provided for a Key Vault reference. The URI of the Key Vault reference is provided and the returned value becomes the value of the secret.

Warning

The use of a KeyVaultSecretProvider overrides the automatic use of the system-assigned managed identity. To use both, you need to use KeyVaultCredentialProvider and return null for the URIs that need resolving.

public class MySecretProvider implements KeyVaultSecretProvider {

    @Override
    public String getSecret(String uri) {
        ...
    }

}

Feature management

Feature management provides a way for Spring Boot applications to dynamically access content. Feature management has various functions such as the following ones:

  • Feature flags that can enable or disable content
  • Feature filters for targeting when content is shown
  • Customized feature filters
  • Feature gates for dynamically enabling endpoints

You can enable feature flags through the following configuration:

spring.cloud.azure.appconfiguration.stores[0].feature-flags.enabled= true

Enabled feature flags are loaded into the Spring configuration system with the prefix feature-management. You can also register feature flags in the local configuration file. For more information, see the Feature flag declaration section.

The easiest way to use feature management is by using the spring-cloud-azure-feature-management and spring-cloud-azure-feature-management-web libraries. The difference between the two libraries is that spring-cloud-azure-feature-management-web takes a dependency on the spring-web and spring-webmvc libraries to add more features, such as feature gates.

You can enable feature flags by using key/label filters. By default, a null label, seen as (No Label), is assigned. You can configure the feature flags that are loaded by setting a label filter, as shown in the following example:

spring.cloud.azure.appconfiguration.stores[0].feature-flags.selects[0].key-filter=A*
spring.cloud.azure.appconfiguration.stores[0].feature-flags.selects[0].label-filter= dev

Feature management basics

Feature flags

Feature flags are composed of two parts: a name and a list of feature-filters that are used to turn on the feature. Feature flags can either have a boolean state of on/off, or they can have a list of feature filters. Feature flags evaluate feature filters until one returns true. If no feature filter returns true, then the feature flag returns false.

Feature filters

Feature filters define a scenario for when a feature should be enabled. Feature filters are evaluated synchronously.

The feature management library comes with four predefined filters: AlwaysOnFilter, PercentageFilter, TimeWindowFilter, and TargetingFilter.

You can create custom feature filters. For example, you can use a feature filter to provide a custom experience for customers who are using a Microsoft Edge browser. You can customize the features in this feature filter, for example, to show a specific header for the Microsoft Edge browser audience.

Feature flag declaration

The feature management library supports Azure App Configuration along with application.yml or bootstrap.yml as sources for feature flags. Here's an example of the format used to set up feature flags in an application.yml file:

feature-management:
  feature-t: false
  feature-u:
    enabled-for:
    - name: Random
  feature-v:
    enabled-for:
    - name: TimeWindowFilter
      parameters:
        Start: "Wed, 01 May 2019 13:59:59 GMT"
        End: "Mon, 01 July 2019 00:00:00 GMT"
  feature-w:
    evaluate: false
    enabled-for:
    - name: AlwaysOnFilter

This example has the following feature flags:

  • feature-t is set to false. This setting always returns the feature flag's value.
  • feature-u is used with feature filters. These filters are defined under the enabled-for property. In this case, feature-u has one feature filter called Random, which doesn't require any configuration, so only the name property is required.
  • feature-v specifies a feature filter named TimeWindowFilter. This feature filter can be passed parameters to use as configuration. In this example, a TimeWindowFilter, passes in the start and end times during which the feature is active.
  • feature-w is used for the AlwaysOnFilter, which always evaluates to true. The evaluate field is used to stop the evaluation of the feature filters, and results in the feature filter always returning false.

Evaluating feature flags

The spring-cloud-azure-feature-management library provides FeatureManager to determine whether a feature flag is enabled. FeatureManager provides an asynchronous way to check the state of the flag.

spring-cloud-azure-feature-management-web, along with providing FeatureManager, contains FeatureManagerSnapshot, which caches the state of previously evaluated feature flags in the @RequestScope to guarantee that all requests return the same value. In addition, the web library provides @FeatureGate, which can either block or redirect web requests to different endpoints.

Feature flag check

FeatureManager is a @Bean that can be @Autowired or injected into @Component type objects. FeatureManager has a method isEnabled that, when passed the name of a feature flag, returns its state.

@Autowired
FeatureManager featureManager;

if (featureManager.isEnabled("feature-t")) {
    // Do Something
}

Note

FeatureManger also has an asynchronous version of isEnabled called isEnabledAsync.

If you haven't configured feature management or the feature flag doesn't exist, isEnabled always returns false. If an existing feature flag is configured with an unknown feature filter, then a FilterNotFoundException is thrown. You can change this behavior to return false by configuring fail-fast to false. The following table describes fail-fast:

Name Description Required Default
spring.cloud.azure.feature.management.fail-fast If an exception occurs, a RuntimeException is thrown. If this property is set to false, then isEnabled returns false instead. No true

The only difference between FeatureManagerSnapshot and FeatureManager is the caching of results in the @RequestScope.

Feature gate

With the feature management web library, you can require that a given feature is enabled in order to execute an endpoint. You can set up this requirement by using the @FeatureGate annotation, as shown in the following example:

@GetMapping("/featureT")
@FeatureGate(feature = "feature-t")
@ResponseBody
public String featureT() {
    ...
}

You can only access the featureT endpoint if "feature-t" is enabled.

Disabled action handling

When an endpoint is blocked because the feature it specifies is disabled, DisabledFeaturesHandler is invoked. By default, an HTTP 404 is returned. You can override this behavior by implementing DisabledFeaturesHandler, as shown in the following example:

@Component
public class MyDisabledFeaturesHandler implements DisabledFeaturesHandler {

    @Override
    public HttpServletResponse handleDisabledFeatures(HttpServletRequest request, HttpServletResponse response) {
        ...
        return response;
    }

}
Routing

Certain routes may expose application capabilities that are gated by features. If a feature is disabled, you can redirect these routes to another endpoint, as shown in the following example:

@GetMapping("/featureT")
@FeatureGate(feature = "feature-t" fallback= "/oldEndpoint")
@ResponseBody
public String featureT() {
    ...
}

@GetMapping("/oldEndpoint")
@ResponseBody
public String oldEndpoint() {
    ...
}

Built-in feature filters

There are a few feature filters that come with the spring-cloud-azure-feature-management package. These feature filters aren't added automatically, but you can set them up in a @Configuration.

AlwaysOnFilter

This filter always returns true. For a usage example, see the feature flag declaration section.

PercentageFilter

Each time a user makes a request, the evaluation of PercentageFilter can return a different result. You can circumvent this inconsistency by using the FeatureManagementSnapshot, which caches the result of the feature flag per user. This capability ensures that a user has a consistent experience, even if they have to resend the request.

feature-management:
  feature-v:
    enabled-for:
    - name: PercentageFilter
      parameters:
        Value: 50

TimeWindowFilter

This filter provides the capability to enable a feature based on a time window. If you specify only End, the feature is considered on until that time. If you specify only Start, the feature is considered on at all points after that time. If you specify both, the feature is considered valid between the two times.

feature-management:
  feature-v:
    enabled-for:
    - name: TimeWindowFilter
      parameters:
        Start: "Wed, 01 May 2019 13:59:59 GMT",
        End: "Mon, 01 July 2019 00:00:00 GMT"

TargetingFilter

This filter provides the capability to enable a feature for a target audience. For an in-depth explanation of targeting, see the targeting section section. The filter parameters include an audience object that describes users, groups, and a default percentage of the user base that should have access to the feature. For each group object that is listed in the target audience, a percentage is required that defines the percentage of that group's members that have access to the feature. A user has the feature enabled in the following cases:

  • The user is specified in the users' section directly.
  • The user is in the included percentage of any of the group rollouts.
  • The user falls into the default rollout percentage.
feature-management: 
  target:
    enabled-for:
    - name: targetingFilter
      parameters:
        users:
        - Jeff
        - Alicia
        groups:
        - name: Ring0
          rollout-percentage: 100
        - name: Ring1
          rolloutPercentage: 100
        default-rollout-percentage: 50

Custom feature filters

Creating a custom feature filter provides a way to enable features based on criteria that you define. To create a custom feature filter, you must implement the FeatureFilter interface. FeatureFilter has a single method evaluate. When a feature specifies that it can be enabled with a feature filter, the evaluate method is called. If evaluate returns true, it means that the feature should be enabled. If it returns false, it continues evaluating feature filters until one returns true. If all filters return false, then the feature is off.

Feature filters are defined as Spring Beans, so they're either defined as @Component or defined in a @Configuration.

@Component("Random")
public class Random implements FeatureFilter {

    @Override
    public boolean evaluate(FeatureFilterEvaluationContext context) {
        double chance = Double.valueOf((String) context.getParameters().get("chance"));
        return Math.random() > chance / 100;
    }

}

Parameterized feature filters

Some feature filters require parameters to determine whether a feature should be turned on. For example, a browser feature filter may turn on a feature for a certain set of browsers. You might want a feature enabled for Microsoft Edge and Chrome browsers, but not Firefox. To set up this situation, you can design a feature filter to expect parameters. These parameters would be specified in the feature configuration and in code, and would be accessible via the FeatureFilterEvaluationContext parameter of evaluate. FeatureFilterEvaluationContext has a property parameters, which is a HashMap<String, Object>.

Targeting

Targeting is a feature management strategy that enables developers to progressively roll out new features to their user base. The strategy is built on the concept of targeting a set of users known as the target audience. An audience is made up of specific users, groups, and a designated percentage of the entire user base. The groups that are included in the audience can be broken down further into percentages of their total members.

The following steps demonstrate an example of a progressive rollout for a new 'Beta' feature:

  1. Individual users Jeff and Alicia are granted access to the Beta.
  2. Another user, Mark, asks to opt in and is included.
  3. Twenty percent of a group known as "Ring1" users are included in the Beta.
  4. The number of "Ring1" users included in the beta is bumped up to 100 percent.
  5. Five percent of the user base is included in the beta.
  6. The rollout percentage is bumped up to 100 percent and the feature is completely rolled out.

This strategy for rolling out a feature is built into the library through the included TargetingFilter feature filter.

Targeting in an application

An example web application that uses the targeting feature filter is available in the example project.

To begin using the TargetingFilter in an application, you must add it as a @Bean like any other feature filter. TargetingFilter relies on another @Bean to be added to the application TargetingContextAccessor. The TargetingContextAccessor allows for defining the current TargetingContext to be used for defining the current user ID and groups, as shown in the following example:

public class MyTargetingContextAccessor implements TargetingContextAccessor {

    @Override
    public void getContextAsync(TargetingContext context) {
        context.setUserId("Jeff");
        ArrayList<String> groups = new ArrayList<String>();
        groups.add("Ring0");
        context.setGroups(groups);
    }

}

Targeting evaluation options

Options are available to customize how targeting evaluation is performed across a given TargetingFilter. You can set an optional parameter, TargetingEvaluationOptions, during the TargetingFilter creation.

    @Bean
    public TargetingFilter targetingFilter(MyTargetingContextAccessor contextAccessor) {
        return new TargetingFilter(contextAccessor, new TargetingEvaluationOptions().setIgnoreCase(true));
    }

Configuration refresh

Enabling config refresh for your configurations lets you pull their latest values from your App Configuration store or stores without having to restart the application.

To enable refresh, you need to enable monitoring along with monitoring triggers. A monitoring trigger is a key with an optional label that's checked for value changes to trigger updates. The value of the monitoring trigger can be any value, as long as it changes when a refresh is needed.

Note

Any operation that changes the ETag of a monitoring trigger causes a refresh, such as a content-type change.

spring:
  cloud:
    azure:
      appconfiguration:
        stores:
        - monitoring:
          enabled: true
          triggers:
          - key: [my-watched-key]
            label: [my-watched-label]

To trigger a configuration refresh, change the value of a key in your configuration store. Then, update one of watch keys to a new value. This change triggers the creation of a log. For example, changing the value of /application/config.message triggers the following log message:

INFO 17496 --- [TaskScheduler-1] o.s.c.e.event.RefreshEventListener       : Refresh keys changed: [config.message]

After the application generates the log, it refreshes all @Beans in the refresh scope.

Note

By default, @ConfigurationProperties annotated beans are included in this scope.

Pull-based refresh

The App Configuration Spring libraries support the ability to periodically check on a refresh interval for changes made to the monitoring triggers. By default, the refresh interval is set to 30 seconds. After the refresh interval has passed, all triggers are checked in the given store for changes. Any change to the key causes a refresh to trigger. Because the libraries integrate with the Spring refresh system, any refresh reloads all configurations from all stores. You can set the refresh interval to any interval longer than 1 second. The supported units for the refresh interval are s, m, h, and d for seconds, minutes, hours, and days respectively. The following example sets the refresh interval to 5 minutes:

spring.cloud.azure.appconfiguration.stores[0].monitoring.refresh-interval= 5m

Automated

When you use the spring-cloud-azure-appconfiguration-config-web library, the application automatically checks for a refresh whenever a servlet request occurs, specifically ServletRequestHandledEvent. The most common way this event is sent is by requests to endpoints in a @RestController.

Manual

In applications that use only spring-cloud-azure-appconfiguration-config, such as console applications, you can manually trigger a refresh by calling AppConfigurationRefresh's refreshConfiguration method. AppConfigurationRefresh is a @Bean that you can inject into any @Component.

Also, because the library uses Spring's configuration system, triggering a refresh causes a refresh of all of your configurations, not just a reload of the ones from your Azure App Configuration store.

Push-based refresh

You can set up the spring-cloud-azure-appconfiguration-config-web library to receive push notifications from your Azure App Configuration store to refresh your configuration values. You can set up this configuration through an Azure Event Grid Web Hook, which you can configure to send notifications of changes to specified keys. By adding the Spring Actuator library as a dependency, you can expose App Configuration's refresh endpoints. There are two different endpoints: appconfiguration-refresh and appconfiguration-refresh-bus. These endpoints work similarly to their counterparts refresh and refresh-bus, where the app configuration endpoints expire the refresh interval instead of forcing a refresh upon receiving. You can still use the refresh and refresh-bus, but you can't connect them directly to Azure Event Grid with a Web Hook because they require a response in setup.

The appconfiguration-refresh property expires the refresh interval, so the remaining refresh interval isn't waited on before the next refresh check. The appconfiguration-refresh-bus property sends a notification to a connected messaging service, such as Azure Service Bus, to notify all instances of an application to refresh. In both cases, it doesn't completely expire at the refresh interval, but is off by a small jitter amount. This jitter ensures that every instance of your application doesn't try to refresh at the same time.

management.endpoints.web.exposure.include= appconfiguration-refresh, appconfiguration-refresh-bus

In addition to exposing the refresh endpoints, a required query parameter has been added for security. No token name or value is set by default, but setting one is required in order to use the endpoints, as shown in the following example:

spring.cloud.azure.appconfiguration.stores[0].monitoring.push-notification.primary-token.name=[primary-token-name]
spring.cloud.azure.appconfiguration.stores[0].monitoring.push-notification.primary-token.secret=[primary-token-secret]
spring.cloud.azure.appconfiguration.stores[0].monitoring.push-notification.secondary-token.name=[secondary-token-name]
spring.cloud.azure.appconfiguration.stores[0].monitoring.push-notification.secondary-token.secret=[secondary-token-secret]

Setting up web hooks

To set up a web hook, open your Azure App Configuration store and open Events from the navigation menu. Then, select Event Subscription. Set the name of your event and select the endpoint type to be Web Hook. Selecting Web Hook causes an Endpoint option to appear. Select Select an endpoint. Your endpoint should look like the following example: https://www.myaplication.com/actuator/appconfiguration-refresh?myTokenName=mySecret.

Confirm Selection sends a setup notification to the given URI and it expects a response. If no response is returned, the setup fails. The azure-spring-cloud-appconfiguration-web library setup for endpoints returns the correct response if the Azure App Configuration store is configured for the application. This confirmation can be sent in other ways. For more information about web hook delivery, see Webhook event delivery.

Note

This validation happens only upon the creation or modification of the endpoint.

We highly recommend that you set up filters because otherwise, a refresh is triggered after every key creation and modification.

Forced client refresh

You can configure the library to force a refresh of all configurations at a refresh interval. The following table describes the refresh-interval property:

Name Description Required Default
spring.cloud.azure.appconfiguration.refresh-interval The standard amount of time between refreshes. Is a Duration. No null

Refreshing with spring.cloud.azure.appconfiguration.refresh-interval doesn't check any configured watch keys. This property is used to make sure Key Vault secrets are kept up to date because Azure App Configuration can't tell when they're updated.

Because Azure Key Vault stores the public and private key pair of a certificate as a secret, your application can retrieve any certificate as a Key Vault reference in App Configuration. Because certificates need to be rotated periodically, client applications need to update just as frequently, which can be done by using the client refresh interval.

Feature flag refresh

If feature flags and monitoring are both enabled, then by default the refresh interval for feature flags is set to 30 seconds. After the refresh interval has passed, all feature flags are checked in the given store for changes. Any change to the key causes a refresh to trigger. Because the libraries integrate with the Spring refresh system, any refresh reloads all configurations from all stores. You can set the refresh interval to any interval longer than 1 second. The supported units for the refresh interval are s, m, h, and d for seconds, minutes, hours, and days respectively. The following example sets the refresh interval to 5 minutes:

spring.cloud.azure.appconfiguration.stores[0].monitoring.feature-flag-refresh-interval= 5m

Health indicator

The client library comes with a health indicator that checks whether the connection to the Azure App Configuration store or stores is healthy. If enabled for each store, it gives one of the following status values:

  • UP - The last connection was successful.
  • DOWN- The last connection resulted in a non-200 error code. This status could be due to issues ranging from credentials expiring to a service issue. The client library automatically retries to connect to the store at the next refresh interval.
  • NOT LOADED - The config store is listed in the local configuration file, but the config store wasn't loaded from the file at startup. The config store is disabled in the configuration file or the configuration or configurations failed to load at startup while the fail-fast configuration for the store was set to false.

You can enable the health indicator by setting management.health.azure-app-configuration.enabled=true.

Client customization

The App Configuration library uses the Azure SDK for Java for connecting to Azure App Configuration and Azure Key Vault. Two interfaces, ConfigurationClientCustomizer and SecretClientCustomizer, are provided to modify the clients. Each interface has a customize method that takes in their respective builder along with the String value of the URI the client is being configured for, as shown in the following interface definitions:

public interface ConfigurationClientCustomizer {
    public void setup(ConfigurationClientBuilder builder, String endpoint);
}

public interface SecretClientCustomizer {
    public void setup(SecretClientBuilder builder, String endpoint);
}

These interfaces allow for customization of the HTTP client and its configurations. The following example replaces the default HttpClient with another one that uses a proxy for all traffic directed to App Configuration and Key Vault.

Note

The ConfigurationClientBuilder and SecretClientBuilder are already set up for use when passed into customize. Any changes to the clients, including the credentials and retry policy, override those already in place.

You can also do this configuration by using Spring Cloud Azure configuration.

public class CustomClient implements ConfigurationClientCustomizer, SecretClientCustomizer {

    @Override
    public void customize(ConfigurationClientBuilder builder, String endpoint) {
        builder.httpClient(buildHttpClient());
    }

    @Override
    public void customize(SecretClientBuilder builder, String endpoint) {
        builder.httpClient(buildHttpClient());
    }

    private HttpClient buildHttpClient() {
        String hostname = System.getProperty("https.proxyHosts");
        String portString = System.getProperty("https.proxyPort");
        int port = Integer.valueOf(portString);

        ProxyOptions proxyOptions = new ProxyOptions(ProxyOptions.Type.HTTP,
                new InetSocketAddress(hostname, port));
        return new NettyAsyncHttpClientBuilder()
                .proxy(proxyOptions)
                .build();
    }

}