Wstęp
Każdy z nas, pracując w niemal każdym projekcie w technologii Spring Boot, codziennie korzysta z plików application.properties czy application.yml. Wykorzystujemy je do pobierania adresu do bazy danych, klucza do zewnętrznego API, czy choćby prostej flagi włączającej nową funkcjonalność. Najczęściej widzimy w kodzie dwa podejścia do pobrania : adnotację @Value i @ConfigurationProperties. Które jest lepsze? Kiedy i dlaczego powinniśmy wybrać jedno zamiast drugiego? W tym wpisie z serii „Szybki strzał” przyjrzymy się obu podejściom i pokażę, w jakich sytuacjach które z nich sprawdzi się najlepiej.
@Value – szybkie rozwiązanie dla pojedynczych wartości
Adnotacja @Value to najprostszy sposób na wstrzyknięcie pojedynczej wartości z pliku konfiguracyjnego prosto do pola w naszym komponencie. Wyobraź sobie, że chcesz wczytać nazwę swojej aplikacji.
W application.properties dodajesz wpis:
spring.application.name="Moja Super Nazwa Aplikacji"
A w kodzie serwisu lub komponentu wstrzykujesz tę wartość:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class AppInfoService {
@Value("${spring.application.name}")
private String appName;
public String getAppName() {
return appName;
}
}
Proste, prawda? @Value świetnie sprawdza się w takich właśnie sytuacjach. Możemy też łatwo zdefiniować wartość domyślną, na wypadek gdyby właściwość nie została zdefiniowana w pliku:
@Value("${feature.toggle.new-dashboard:false}")
private boolean newDashboardEnabled;
Kiedy @Value staje się problemem? Gdy mamy do czynienia z grupą powiązanych właściwości. Wyobraź sobie konfigurację klienta do zewnętrznego API: URL, klucz, sekret, timeout i tak dalej. Wstrzykiwanie każdej z tych wartości osobno za pomocą @Value szybko prowadzi do bałaganu i powtórzeń. Co więcej, nie mamy tu wbudowanej walidacji, a kod staje się mniej czytelny. Dlatego pod takie sytuacji stworzony inny sposób wstrzykiwania wartości poprzez użycie adnotacji @ConfigurationProperties.
@ConfigurationProperties w Spring Boot – dla złożonych konfiguracji
@ConfigurationProperties zamiast wstrzykiwać pojedyncze stringi, możemy zmapować całą gałąź konfiguracji na dedykowany, typowany obiekt. To podejście, które zdecydowanie polecam w większości przypadków.
Zobaczmy to na przykładzie konfiguracji serwera pocztowego. Tym razem wykorzystamy plik application.yml i zdefiniujemy w nim właściwości, co ważne, pod wspólnym prefiksem.
uprogramisty:
mail:
host: smtp.example.com
port: 587
username: admin
enabled: true
Następnie tworzymy klase, która będzie reprezentować te ustawienia:
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "uprogramisty.mail")
public class MailProperties {
@NotEmpty
private String host;
@Min(1)
private int port;
private String username;
private boolean enabled;
public MailProperties() {
}
// Gettery i Settery
}
I to wszystko. Taki komponent możemy sobie wstrzyknąć w dowolne miejsce i dostajemy informację zawarte w naszym pliku konfiguracyjnym.
@Service
public class NotificationService {
private final MailProperties mailProperties;
public NotificationService(MailProperties mailProperties) {
this.mailProperties = mailProperties;
// Teraz możemy używać np. mailProperties.host()
}
}
Warto też tutaj zwrócić uwagę na adnotacje walidacyjne, takie jak @NotEmpty czy @Min. Jeśli dodamy jeszcze @Validated do naszej klasy MailProperties, Spring automatycznie sprawdzi poprawność wartości podczas uruchamiania aplikacji (wymagana dodatkowa zależność w projekcie np. org.hibernate.validator:hibernate-validator). Genialne w swojej prostocie! Przykładowo jak w zmiennej port ustawimy wartość 0 oraz w zmiennej host zostawimy pustą wartość to dostaniemy błąd przy starcie aplikacji:

Na koniec taka mała dygresja. Całą konfigurację z użyciem @ConfigurationProperties zamknęliśmy w jednej klasie ze względu na to, że dodatkowo dołożyliśmy adnotacje @Component. Spring Boot automatycznie wykryje taką klasę z @ConfigurationProperties i ją zarejestruje. Dzięki temu możemy swobodnie jej używać. Podobnie byłoby w sytuacji jakbyśmy ręcznie utworzyli taką klasę jako bean z użyciem adnotacji @Bean. Efekt byłby taki sam.
Natomiast jeśli z jakiegoś powodu nie chcielibyśmy tak robić to istnieją na to inne mechanizmy, aby Spring mógł wykryć tego typu konfiguracje. W takiej sytuacji możemy użyć adnotacji @EnableConfigurationProperties. Wtedy informujemy Springa, że ma taką klasę przetworzyć, dodać ją do swojego kontekstu i wstrzyknąć odpowiednie wartości. Taką adnotację dodajemy w klasie konfiguracyjnej albo też możemy dodać w głównej klasie startującej całą aplikację:
@Configuration
@EnableConfigurationProperties(MailProperties.class)
public class AppConfig {
// ... inne konfiguracje
}
// Albo alternatywnie możemy dodać to w klasie Application
@SpringBootApplication
@EnableConfigurationProperties(MailProperties.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Użycie klasy Rekord, zamiast zwykłej klasy
Jeśli pracujemy na nowszej wersji Javy to zamiast używania zwykłej klasy do wstrzykiwania danych z pliku konfiguracyjnego można też wykorzystać klasy Rekord do tego. Takie rozwiązanie, przynajmniej według mnie, wydaje się dużo czytelniejsze niż używania standardowego podejścia. W końcu po coś te Rekordy dodawali.
Tak, więc naszą poprzednio utworzoną konfiguracje możemy zapisać teraz w taki sposób:
import org.springframework.boot.context.properties.ConfigurationProperties;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Min;
@ConfigurationProperties(prefix = "uprogramisty.mail")
public record MailPropertiesRecord(
@NotEmpty String host,
@Min(1) int port,
String username,
boolean enabled
) {
}
Aby nam działała wersja z rekordami to potrzebujemy dodatkową konfiguracja, żeby Springa mógł wykryć tego typu wstrzykiwanie danych. Najprościej jest dodać adnotację @ConfigurationPropertiesScan w klasie konfiguracyjnej albo też można dodać tą adnotację w głównej klasie startującej całą aplikację:
@Configuration
@ConfigurationPropertiesScan
public class AppConfig {
// ... inne konfiguracje
}
// Albo alternatywnie możemy dodać to w klasie Application
@SpringBootApplication
@ConfigurationPropertiesScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Kiedy używać którego rozwiązania?
Kiedy najlepiej używać @Value:
- Potrzebujesz 1-3 prostych wartości
- Potrzebujesz tylko kilku, niepowiązanych ze sobą wartości
- Konfiguracja jest prosta i nie będzie się rozrastać
- Wartości są używane w pojedynczym komponencie
Natomiast @ConfigurationProperties dobrze użyć, gdy:
- Masz grupę logicznie powiązanych właściwości
- Chcesz mieć typ-safe dostęp do konfiguracji
- Potrzebujesz nested objects
- Konfiguracja jest używana w wielu miejscach
W praktyce często widzę mieszane podejście – @ConfigurationProperties dla głównych bloków konfiguracyjnych (database, security, external APIs) i @Value dla pojedynczych flag czy prostych wartości.
Wskazówki
Poniżej kilka wskazówek, które mogą się przydać podczas projektowania:
Relaxed binding – Spring Boot automatycznie mapuje różne formaty. Właściwość my-property, myProperty, MY_PROPERTY czy my_property – wszystkie będą zmapowane na pole myProperty w klasie.
Prefix organization – Organizowanie konfiguracji hierarchicznie. Zamiast emailHost, emailPort lepiej jest używać app.email.host, app.email.port. To ułatwi późniejszy refaktoring.
Immutable Configuration – Jak pracujemy z nowszymi wersja Javy (14+) dobrze jest rozważyć użycie record’ów dla @ConfigurationProperties:
@ConfigurationProperties(prefix = "app.email")
public record EmailProperties(
String from,
int timeout,
boolean enabled,
SmtpProperties smtp
) {
public record SmtpProperties(String host, int port) {}
}
Environment-specific configs – W naszym projekcie dobrze jest też wykorzystywać profile do wstrzykiwania odpowiednich konfiguracji w zależności od środowiska np. application-dev.properties, application-prod.properties.
application.properties vs application.yml
W konfiguracji Spring Boota możemy używać dwóch typów plików do przygotowywania konfiguracji aplikacji. Niezależnie od wyboru danego typu, efekt końcowy będzie taki sam. Natomiast różnica w nich polega na czytelności.
W przypadku application.properties mamy dość prostą strukturę. Każde zagnieżdżenie konfiguracji oddzielamy kropką:
app.email.from=noreply@firma.pl app.email.timeout=5000 app.email.smtp.host=smtp.gmail.com app.email.smtp.port=587
W przypadku application.yml mamy bardziej czytelną strukturę dla złożonych struktur:
app:
email:
from: noreply@firma.pl
timeout: 5000
smtp:
host: smtp.gmail.com
port: 587
Z mojego doświadczenia dla prostych projektów jest dobrze zostać przy .properties. W przypadku typu YAML zyskuje sens, gdy mamy dużo zagnieżdżonych właściwości (nestes properties) lub używamy takich struktur jak list czy map w konfiguracji.
Podsumowanie
Ostatecznie wybór pomiędzy @Value a @ConfigurationProperties sprowadza się do tego, jak bardzo rozbudowaną masz konfigurację. Do prostych wartości @Value sprawdza się idealnie – wrzucasz, działa i nie zawracasz sobie głowy. Ale gdy tych ustawień robi się więcej, lepiej od razu użyć adnotacji @ConfigurationProperties. Masz wtedy wszystko w jednym miejscu, większą czytelność i spokój, że typy się zgadzają.
Warto też nie zapominać o wartościach domyślnych i sensownych nazwach kluczy – serio, to później oszczędza sporo nerwów, zwłaszcza jak konfiguracja rośnie albo ktoś inny musi się w tym odnaleźć.
Mam nadzieję, że ten szybki strzał rozwiał Twoje wątpliwości. Daj znać w komentarzu, jakie konfiguracje sprawiły Ci najwięcej zabawy albo jak Ty podchodzisz do tematu w swoich projektach!