The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

Mapeando configuração para objetos

Com os mapeamentos de configuração, é possível agrupar várias propriedades de configuração em uma única interface que compartilha o mesmo prefixo.

1. @ConfigMapping

A config mapping requires an interface with minimal metadata configuration and annotated with the @io.smallrye.config.ConfigMapping annotation.

@ConfigMapping(prefix = "server")
interface Server {
    String host();

    int port();
}

A interface Server é capaz de mapear as propriedades de configuração com o nome server.host para o método Server.host() e server.port para o método Server.port(). O nome da propriedade de configuração a ser pesquisada é criado a partir do prefixo e do nome do método com . (ponto) como separador.

Se um mapeamento não corresponder a uma propriedade de configuração, será lançado um NoSuchElementException, a menos que o elemento mapeado seja um Optional.

1.1. Registro

Quando um aplicativo Quarkus é iniciado, um mapeamento de configuração pode ser registrado duas vezes. Uma vez para STATIC INIT e uma segunda vez para RUNTIME INIT :

1.1.1. STATIC INIT

O Quarkus inicia alguns de seus serviços durante a inicialização estática, e Config geralmente é uma das primeiras coisas criadas. Em determinadas situações, pode não ser possível inicializar corretamente um mapeamento de configuração. Por exemplo, se o mapeamento exigir valores de um ConfigSource personalizado. Por esse motivo, qualquer mapeamento de configuração requer a anotação @io.quarkus.runtime.configuration.StaticInitSafe para marcar o mapeamento como seguro para ser usado nesse estágio. Saiba mais sobre o registro de um ConfigSource personalizado.

1.1.1.1. Exemplo
@StaticInitSafe
@ConfigMapping(prefix = "server")
interface Server {
    String host();

    int port();
}

1.1.2. RUNTIME INIT

O estágio RUNTIME INIT ocorre após o STATIC INIT. Não há restrições nesse estágio, e qualquer mapeamento de configuração é adicionado à instância Config conforme esperado.

1.2. Recuperação

Uma interface de mapeamento de configuração pode ser injetada em qualquer bean com reconhecimento de CDI:

class BusinessBean {
    @Inject
    Server server;

    public void businessMethod() {
        String host = server.host();
    }
}

Em contextos não-CDI, use a API io.smallrye.config.SmallRyeConfig#getConfigMapping para recuperar a instância de mapeamento de configuração:

SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class);
Server server = config.getConfigMapping(Server.class);

1.3. Grupos aninhados

Um mapeamento aninhado oferece uma maneira de subgrupar outras propriedades de configuração:

@ConfigMapping(prefix = "server")
public interface Server {
    String host();

    int port();

    Log log();

    interface Log {
        boolean enabled();

        String suffix();

        boolean rotate();
    }
}
application.properties
server.host=localhost
server.port=8080
server.log.enabled=true
server.log.suffix=.log
server.log.rotate=false

O nome do método de um grupo de mapeamento atua como sub-namespace para as propriedades de configuração.

1.4. Sobrescrevendo nomes de propriedades

1.4.1. @WithName

Se o nome de um método ou o nome de uma propriedade não corresponderem um ao outro, a anotação @WithName poderá substituir o mapeamento do nome do método e usar o nome fornecido na anotação:

@ConfigMapping(prefix = "server")
interface Server {
    @WithName("name")
    String host();

    int port();
}
application.properties
server.name=localhost
server.port=8080

1.4.2. @WithParentName

A anotação @WithParentName permite que o mapeamento de configurações herde seu nome de contêiner, simplificando o nome da propriedade de configuração necessária para corresponder ao mapeamento:

interface Server {
    @WithParentName
    ServerHostAndPort hostAndPort();

    @WithParentName
    ServerInfo info();
}

interface ServerHostAndPort {
    String host();

    int port();
}

interface ServerInfo {
    String name();
}
application.properties
server.host=localhost
server.port=8080
server.name=konoha

Sem o @WithParentName, o método name() requer a propriedade de configuração server.info.name. Como usamos @WithParentName, o mapeamento info() herdará o nome pai de Server e name() mapeará para server.name.

1.4.3. NamingStrategy

Os nomes de métodos em camelCase são mapeados para nomes de propriedades em kebab-case:

@ConfigMapping(prefix = "server")
interface Server {
    String theHost();

    int thePort();
}
application.properties
server.the-host=localhost
server.the-port=8080

A estratégia de mapeamento pode ser ajustada com a configuração do valor namingStrategy na anotação @ConfigMapping:

@ConfigMapping(prefix = "server", namingStrategy = ConfigMapping.NamingStrategy.VERBATIM)
public interface ServerVerbatimNamingStrategy {
    String theHost();

    int thePort();
}
application.properties
server.theHost=localhost
server.thePort=8080

A anotação @ConfigMapping é compatível com as seguintes estratégias de nomeação:

  • KEBAB_CASE (padrão) - O nome do método é derivado substituindo as mudanças de maiúsculas por um traço para mapear a propriedade de configuração.

  • VERBATIM - O nome do método é usado como está para mapear a propriedade de configuração.

  • SNAKE_CASE - O nome do método é derivado pela substituição das mudanças de maiúsculas por um sublinhado para mapear a propriedade de configuração.

1.5. Conversões

Uma classe de mapeamento de configuração suporta conversões automáticas de todos os tipos disponíveis para conversão em Config:

@ConfigMapping
public interface SomeTypes {
    @WithName("int")
    int intPrimitive();

    @WithName("int")
    Integer intWrapper();

    @WithName("long")
    long longPrimitive();

    @WithName("long")
    Long longWrapper();

    @WithName("float")
    float floatPrimitive();

    @WithName("float")
    Float floatWrapper();

    @WithName("double")
    double doublePrimitive();

    @WithName("double")
    Double doubleWrapper();

    @WithName("char")
    char charPrimitive();

    @WithName("char")
    Character charWrapper();

    @WithName("boolean")
    boolean booleanPrimitive();

    @WithName("boolean")
    Boolean booleanWrapper();
}
application.properties
int=9
long=9999999999
float=99.9
double=99.99
char=c
boolean=true

Isso também é válido para Optional e amigos:

@ConfigMapping
public interface Optionals {
    Optional<Server> server();

    Optional<String> optional();

    @WithName("optional.int")
    OptionalInt optionalInt();

    interface Server {
        String host();

        int port();
    }
}

Nesse caso, o mapeamento não falhará se não houver nenhuma propriedade de configuração que corresponda ao mapeamento.

1.5.1. @WithConverter

A anotação @WithConverter oferece uma maneira de definir um Converter para ser usado em um mapeamento específico:

@ConfigMapping
public interface Converters {
    @WithConverter(FooBarConverter.class)
    String foo();
}

public static class FooBarConverter implements Converter<String> {
    @Override
    public String convert(final String value) {
        return "bar";
    }
}
application.properties
foo=foo

Uma chamada para Converters.foo() resulta no valor bar.

1.5.2. Coleções

Um mapeamento de configuração também é capaz de mapear os tipos de coleções List e Set:

@ConfigMapping(prefix = "server")
public interface ServerCollections {
    Set<Environment> environments();

    interface Environment {
        String name();

        List<App> apps();

        interface App {
            String name();

            List<String> services();

            Optional<List<String>> databases();
        }
    }
}
application.properties
server.environments[0].name=dev
server.environments[0].apps[0].name=rest
server.environments[0].apps[0].services=bookstore,registration
server.environments[0].apps[0].databases=pg,h2
server.environments[0].apps[1].name=batch
server.environments[0].apps[1].services=stock,warehouse

Os mapeamentos List ou Set podem usar propriedades indexadas para mapear valores de configuração em grupos de mapeamento. Para coleções com tipos de elementos simples como String, seu valor de configuração é uma cadeia de caracteres separada por vírgulas.

1.5.3. Mapas

Um mapeamento de configuração também é capaz de mapear um Map:

@ConfigMapping(prefix = "server")
public interface Server {
    String host();

    int port();

    Map<String, String> form();
}
application.properties
server.host=localhost
server.port=8080
server.form.login-page=login.html
server.form.error-page=error.html
server.form.landing-page=index.html

A propriedade de configuração precisa especificar um nome adicional para atuar como chave. Nesse caso, o form() Map conterá três elementos com as chaves login-page, error-page e landing-page.

1.6. Padrões

A anotação @WithDefault permite definir uma propriedade padrão em um mapeamento (e evitar um erro se o valor da configuração não estiver disponível em nenhum ConfigSource):

public interface Defaults {
    @WithDefault("foo")
    String foo();

    @WithDefault("bar")
    String bar();
}

Não são necessárias propriedades de configuração. O Defaults.foo() retornará o valor foo e o Defaults.bar() retornará o valor bar.

1.7. Validação

Um mapeamento de configuração pode combinar anotações do Bean Validation para validar os valores de configuração:

@ConfigMapping(prefix = "server")
interface Server {
    @Size(min = 2, max = 20)
    String host();

    @Max(10000)
    int port();
}
Para que a validação funcione, é necessária a extensão quarkus-hibernate-validator, que é executada automaticamente.

1.8. Mocking (Simulação)

Uma implementação de interface de mapeamento não é um proxy, portanto, não pode ser simulada diretamente com @InjectMock como outros beans CDI. Um truque é torná-la proxy com um método produtor:

public class ServerMockProducer {
    @Inject
    Config config;

    @Produces
    @ApplicationScoped
    @io.quarkus.test.Mock
    Server server() {
        return config.unwrap(SmallRyeConfig.class).getConfigMapping(Server.class);
    }
}

O Server pode ser injetado como uma simulação em uma classe de teste do Quarkus com @InjectMock:

@QuarkusTest
class ServerMockTest {
    @InjectMock
    Server server;

    @Test
    void localhost() {
        Mockito.when(server.host()).thenReturn("localhost");
        assertEquals("localhost", server.host());
    }
}
O mock (simulação) é apenas uma casca vazia sem nenhum valor de configuração real.

Se o objetivo for apenas simular determinados valores de configuração e manter a configuração original, a instância de simulação exigirá um spy (espião):

@ConfigMapping(prefix = "app")
public interface AppConfig {
    @WithDefault("app")
    String name();

    Info info();

    interface Info {
        @WithDefault("alias")
        String alias();
        @WithDefault("10")
        Integer count();
    }
}

public static class AppConfigProducer {
    @Inject
    Config config;

    @Produces
    @ApplicationScoped
    @io.quarkus.test.Mock
    AppConfig appConfig() {
        AppConfig appConfig = config.unwrap(SmallRyeConfig.class).getConfigMapping(AppConfig.class);
        AppConfig appConfigSpy = Mockito.spy(appConfig);
        AppConfig.Info infoSpy = Mockito.spy(appConfig.info());
        Mockito.when(appConfigSpy.info()).thenReturn(infoSpy);
        return appConfigSpy;
    }
}

O AppConfig pode ser injetado como uma simulação em uma classe de teste do Quarkus com @Inject:

@QuarkusTest
class AppConfigTest {
    @Inject
    AppConfig appConfig;

    @Test
    void localhost() {
        Mockito.when(appConfig.name()).thenReturn("mocked-app");
        assertEquals("mocked-app", server.host());

        Mockito.when(appConfig.info().alias()).thenReturn("mocked-alias");
        assertEquals("mocked-alias", server.info().alias());
    }
}
Os elementos aninhados precisam ser espionados individualmente pelo Mockito.

2. @ConfigProperties (Deprecated)

This feature will be removed soon, please update your code base and use @ConfigMapping instead.

The @io.quarkus.arc.config.ConfigProperties annotation is able to group multiple related configuration values in its own class:

package org.acme.config;

import io.quarkus.arc.config.ConfigProperties;
import java.util.Optional;

@ConfigProperties(prefix = "greeting") (1)
public class GreetingConfiguration {

    private String message;
    private String suffix = "!"; (2)
    private Optional<String> name;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }

    public Optional<String> getName() {
        return name;
    }

    public void setName(Optional<String> name) {
        this.name = name;
    }
}
1 prefix is optional. If not set then the prefix to be used will be determined by the class name. In this case it would still be greeting (since the Configuration suffix is removed). If the class were named GreetingExtraConfiguration then the resulting default prefix would be greeting-extra.
2 ! will be the default value if greeting.suffix is not set.

Inject the GreetingResource with CDI @Inject:

@Inject
GreetingConfiguration greetingConfiguration;

Another alternative style provided by Quarkus is to create GreetingConfiguration as an interface:

package org.acme.config;

import io.quarkus.arc.config.ConfigProperties;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import java.util.Optional;

@ConfigProperties(prefix = "greeting")
public interface GreetingConfiguration {

    @ConfigProperty(name = "message") (1)
    String message();

    @ConfigProperty(defaultValue = "!")
    String getSuffix(); (2)

    Optional<String> getName(); (3)
}
1 The @ConfigProperty annotation is needed because the name of the configuration property that the method corresponds does not follow the getter method naming conventions.
2 In this case since name is not set, the corresponding property will be greeting.suffix.
3 It is unnecessary to specify the @ConfigProperty annotation because the method name follows the getter method naming conventions (greeting.name being the corresponding property) and no default value is required.

When using @ConfigProperties on a class or an interface, if the value of one of its fields is not provided, the application startup will fail, and a javax.enterprise.inject.spi.DeploymentException will be thrown. This does not apply to Optional fields and fields with a default value.

2.1. Additional notes on @ConfigProperties

When using a regular class annotated with @ConfigProperties the class doesn’t necessarily have to declare getters and setters. Having simple public non-final fields is valid as well.

Furthermore, the configuration classes support nested object configuration. Suppose there was a need to have an extra layer of greeting configuration named content that would contain a few fields:

@ConfigProperties(prefix = "greeting")
public class GreetingConfiguration {

    public String message;
    public String suffix = "!";
    public Optional<String> name;
    public ContentConfig content; (1)

    public static class ContentConfig {
        public Integer prizeAmount;
        public List<String> recipients;
    }
}
1 The name of the field (not the class name) will determine the name of the properties that are bound to the object.

Setting the properties would occur in the normal manner:

application.properties
greeting.message = hello
greeting.name = quarkus
greeting.content.prize-amount=10
greeting.content.recipients=Jane,John

Furthermore, classes annotated with @ConfigProperties can be annotated with Bean Validation annotations:

@ConfigProperties(prefix = "greeting")
public class GreetingConfiguration {

    @Size(min = 20)
    public String message;
    public String suffix = "!";

}
For validation to work, the quarkus-hibernate-validator extension is required.

2.2. Using same ConfigProperties with different prefixes

Quarkus also supports the use of the same @ConfigProperties object with different prefixes for each injection point using the io.quarkus.arc.config.@ConfigPrefix annotation. If GreetingConfiguration from above needs to be used for both the greeting prefix and the other prefix:

@ConfigProperties(prefix = "greeting")
public class GreetingConfiguration {

    @Size(min = 20)
    public String message;
    public String suffix = "!";

}
@ApplicationScoped
public class SomeBean {

    @Inject (1)
    GreetingConfiguration greetingConfiguration;

    @ConfigPrefix("other") (2)
    GreetingConfiguration otherConfiguration;

}
1 At this injection point greetingConfiguration will use the greeting prefix since that is what has been defined on @ConfigProperties.
2 At this injection point otherConfiguration will use the other prefix from @ConfigPrefix instead of the greeting prefix. Notice that in this case @Inject is not required.

2.3. Combining ConfigProperties with build time conditions

Quarkus allows you to define conditions evaluated at build time (@IfBuildProfile, @UnlessBuildProfile, @IfBuildProperty and @UnlessBuildProperty) to enable or not the annotations @ConfigProperties and @ConfigPrefix which gives you a very flexible way to map your configuration.

Let’s assume that the configuration of a service is mapped thanks to a @ConfigProperties and you don’t need this part of the configuration for your tests as it will be mocked, in that case you can define a build time condition like in the next example:

ServiceConfiguration.java

@UnlessBuildProfile("test") (1)
@ConfigProperties
public class ServiceConfiguration {
    public String user;
    public String password;
}
1 The annotation @ConfigProperties is considered if and only if the active profile is not test.

SomeBean.java

@ApplicationScoped
public class SomeBean {

    @Inject
    Instance<ServiceConfiguration> serviceConfiguration; (1)

}
1 As the configuration of the service could be missing, we need to use Instance<ServiceConfiguration> as type at the injection point.