java
48 lines · 6 steps
Type-safe config with Spring records
A Java record binds and validates payments.gateway.* properties at startup using Spring Boot's @ConfigurationProperties.
Explained by
highlit
1package com.example.payments.config;
2
3import jakarta.validation.constraints.Min;
4import jakarta.validation.constraints.NotBlank;
5import jakarta.validation.constraints.NotNull;
6import java.time.Duration;
7import java.util.List;
8import org.springframework.boot.context.properties.ConfigurationProperties;
9import org.springframework.boot.context.properties.bind.DefaultValue;
10import org.springframework.validation.annotation.Validated;
11
12@Validated
13@ConfigurationProperties(prefix = "payments.gateway")
14public record PaymentGatewayProperties(
15
16 @NotBlank
17 String baseUrl,
18
19 @NotBlank
20 String apiKey,
21
22 @NotNull
23 @DefaultValue("5s")
24 Duration connectTimeout,
25
26 @NotNull
27 @DefaultValue("30s")
28 Duration readTimeout,
29
30 @Min(0)
31 @DefaultValue("3")
32 int maxRetries,
33
34 @DefaultValue("true")
35 boolean tlsVerification,
36
37 @NotNull
38 Retry retry,
39
40 @DefaultValue
41 List<String> supportedCurrencies
42) {
43
44 public record Retry(
45 @Min(1) @DefaultValue("2") int attempts,
46 @NotNull @DefaultValue("500ms") Duration backoff
47 ) {}
48}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Java records make immutable configuration objects where each component is a named, typed property.
- 2Combining @Validated with Jakarta constraints fails fast at startup when config is missing or invalid.
- 3@DefaultValue lets Spring supply sensible fallbacks so only secrets and overrides need to be set.
Related explainers
typescript
import { NestFactory } from '@nestjs/core'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { ValidationPipe } from '@nestjs/common'; import { ApiProperty } from '@nestjs/swagger';
Wiring validation and Swagger docs in NestJS
validation
openapi
decorators
Intermediate
8 steps
java
public interface GitHubClient { @GetExchange("/users/{username}") GitHubUser getUser(@PathVariable String username);
Declarative HTTP clients in Spring
http-client
declarative-api
proxy
Intermediate
8 steps
java
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;
Building a trie for autocomplete in Java
trie
prefix-tree
recursion
Intermediate
8 steps
ruby
class KeyTransformer def self.camelize(data) new.camelize(data) end
Recursively camelizing nested Ruby data
recursion
data-transformation
pattern-matching
Intermediate
7 steps
java
@RestController @RequestMapping("/api/products") public class ProductSearchController {
Binding collection query params in Spring
rest-api
query-parameters
dependency-injection
Intermediate
6 steps
python
import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent.parent
How a Django settings module is wired
configuration
environment-variables
middleware
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/type-safe-config-with-spring-records-explained-java-77aa/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.