Spring Boot's Coding Tips and Tricks

Wanna have fun with Spring Boot? Check those tips and tricks out!
For more dedicated coding tips and tricks in general, see this article.
Use Java Records for Configuration Properties
Because Life's Too Short for Boilerplate
If you're lucky enough to be running Spring Boot 3+ (which demands Java 17+ like a bouncer at an exclusive club), then it's time to embrace the sheer elegance of Java Records!
Step 1: Define Your application.yaml File
.yaml or .yml is the future, and .properties files belong in a museum next to floppy disks:
application-properties:
s3-bucket-name: 'my-s3-bucket'
sqs-topics:
- 'my-topic-1'
- 'my-topic-2'
no-privilege-method-urls:
- { method: DELETE, url: '/auth/logout/**' }
- { method: POST, url: '/auth/login/**' }
security:
jwt-expiration-time: 15m
password-reset-code-duration: 1d
Step 2: Create Your Record (The Magic Happens Here)
@ConfigurationProperties(prefix = "application-properties")
public record ApplicationProperties(
String s3BucketName,
List<String> sqsTopics,
List<MethodUrl> noPrivilegeMethodUrls,
Security security) {
// Implicitly static - no need to spam "static" everywhere like it's 2005
public record MethodUrl(org.springframework.http.HttpMethod method, String url) {}
// Another implicitly static nested record, because we're fancy like that
public record Security(Duration jwtExpirationTime, Duration passwordResetCodeDuration) {}
}
Binding Magic Explained (for the curious minds):
Details
Spring Boot is surprisingly smart about this stuff:
-
Write your YAML in
kebab-case(the-cool-way), code your record fields incamelCase(theCoolWay). Spring Boot handles the translation like a diplomatic genius. -
s3-bucket-nameis just text, so it becomes aString. Easy peasy. -
sqs-topicsis a list of strings, so we useList<String>. You could also useCollection<String>orSet<String>if you're feeling adventurous. -
no-privilege-method-urlsis where things get spicy - it's a list of objects withmethod(an enum) andurl(a string) fields. We create a customList<MethodUrl>whereMethodUrlis our inner record. Nested records are like Russian dolls, but useful. -
securityis a parent property with two child properties, so we create aSecurityrecord withDuration jwtExpirationTimeandDuration passwordResetCodeDuration.- Bonus points: Spring Boot automatically converts human-readable time strings (
2m,3d,4h30m) into properDurationobjects. It's like having a personal assistant for your time conversions!
- Bonus points: Spring Boot automatically converts human-readable time strings (
Step 3: Register with the Main Class (The Grand Finale)
Just slap an @EnableConfigurationProperties on your main class like this and call it a day:
@SpringBootApplication
@EnableConfigurationProperties(ApplicationProperties.class)
public class MySpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(MySpringBootApplication.class, args);
}
}
You can actually use Java Records to be your configuration properties since Spring Boot 2.6, but some projects will still be using JDK 8 or JDK 11, and thus, Java Records are not available to them.
Want the full tutorial with all the bells and whistles? Check out this excellent Baeldung article where they explain everything with the patience of a saint.
Prefer Constructor Injection Over Setter or Field Injection
This advice appears in many blog posts, so I'll keep it brief: use constructor injection everywhere. If your class has more than 255 dependencies (the hard limit number of parameters a method can have before Java refuses to compile), consider whether your class violates the Single Responsibility Principle, or reconsider your career choices.
One thing I'd like to add: if you're using Lombok in your project (which is likely), use this:
@Service
@RequiredArgsConstructor
public class MyCoolService {
private final MyCoolRepository myCoolRepository;
}
instead of the manually written all-args constructor like this:
@Service
public class MyCoolService {
private final MyCoolRepository myCoolRepository;
public MyCoolService(MyCoolRepository myCoolRepository) {
this.myCoolRepository = myCoolRepository;
}
}
There are cases where you need to use a manual constructor, for example, if the beans have their own @Qualifier (though you'd be better off restructuring your bean configuration than manually using the @Qualifier annotation), or when writing integration tests (where you'll have to use @Autowired because there are no other options). But for most purposes, @RequiredArgsConstructor is sufficient to keep your classes clean, neat, and beautiful.
Benefits
-
You cannot accidentally reassign dependencies because you can use
finalfields. -
The app will fail-fast instead of blowing up in the middle of an important transaction due to a nasty
NullPointerException. Constructor injection ensures that you cannot start the app if even one required bean is configured incorrectly. -
Along the same lines, it helps detect circular dependencies.
-
You'll have an easier time mocking (assuming you use Mockito): add
@Mockto your dependencies, then@InjectMocksto the class you want to test. It's the easiest way to write unit tests. -
Using Lombok can hide the SonarQube warning about having more than 7 constructor parameters, but seriously, if that is the case, then that's a good sign that your class is becoming overburdened and refactoring should be done to adhere to the Single Responsibility Principle.
Leave a comment below, and tell me some of the tips and tricks you've been using to great successes!