Spring Boot Best Practices for New Developers 2026

 

I have already written many articles about Spring Boot. In this article, I will combine some of the best practices with new features.

✅ Feature-based folder structure

  • Feature-based structure is highly recommended for microservices-based larger projects.
  • Because it will be easy to navigate, first your feature, then the relevant controller or resolver.
  • This approach will improve the modularity and team collaboration
  • Because different teams can work in different modules with minimal conflicts.
  • Your application will look clean, promoting Domain Driven Architecture.
  • Here is a sample structure.

✅ Use Spring Boot starters

  • Spring Boot starters are bundled with a set of required dependencies as a single dependency.
  • So you do not need to add dependencies one by one like you did before with Spring Core. Instead, you can use a starter, which is like a bundle.

Look at the spring-boot-starter-web dependency. You can run mvn dependency: tree to view the dependencies.

Press enter or click to view image in full size

✅ Use constructor injection

  • I have been using Lombok @RequiredArgsConstructor when injecting dependencies.
  • But without binding with an additional dependency, you can use constructor injection with explicit constructors.
  • Because it allows the application to initialize all required dependencies at the initialization time, so you do not need to worry about unit testing.
  • I believe you may not be using setter or field injection anymore.
Press enter or click to view image in full size

✅ Use Java Records for DTOs

  • I am not saying you should use Records for DTOs. It depends. But records are more readable and cleaner than using traditional POJOs
  • Java records are bundled with equals(), hashCode(), toString(), and getters.
  • But records are immutable; it doesn’t have setters.

✅ Use the proper version of the dependencies

  • Always use the latest stable GA versions.
  • But if you are thinking about backward compatibility, you need to review the dependency with your requirements.
  • Because some dependencies may be deprecated in newer versions.
  • It is not recommended to override versions due to version conflicts.
  • Centralize your dependency versions with a property.
<properties>
<jjwt.version>0.12.6<jjwt.version>
</properties>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>{jjwt.version}</version>
</dependency>


<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>{jjwt.version}</version>
<scope>runtime</scope>
</dependency>

✅ Use @ConfigurationProperties to externalize properties

  • Externalizing configuration is a crucial aspect of application development, as it allows you to modify settings without the need to redeploy your application.
  • In Spring Boot, configuration values are typically defined in a property file or yaml file.
  • To access these values within your application, the @Value annotation is commonly used for injecting configuration properties into your components.
@Value("${app.name}")
private String appName;
  • I prefer using the @ConfigurationProperties annotation to map your configuration details into an object. And then you can easily use it wherever you want.
  • This also provides compile-time safety.
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "demo-app-config")
@Data()
public class DemoAppConfig {
private String name;
private String version;
private String author;
}

Here is the application.yml file.

demoAppConfig:
name: DemoApp
version: 1.0v
author: John

✅ Use slf4j logging

  • Logging is important because if a problem occurs while your application is in production, logging is the only way to find out the root cause.
  • Therefore, you should think carefully before adding loggers, log message types, logger levels, and logger messages.
  • Do not use System.out.print(); always use meaningful log messages.
  • Slf4j is recommended to use along with logback, which is the default logging framework in Spring Boot.
  • Always use slf4j { } and avoid using String interpolation in logger messages. Because string interpolation consumes more memory.
  • You can use the Lombok @Slf4j annotation to easily create a logger.
  • If you are in a microservices environment, you can use a centralized logging system like the ELK stack.

✅ Use centralized exception handling

  • This is very important when working with large enterprise-level applications.
  • Apart from the general exceptions, we can have some scenarios to identify some specific error cases based on the application.
  • An exception adviser can be created with @ControllerAdvice and we can create separate exceptions with meaningful details.
  • It will make it much easier to identify and debug errors in the future.
import lombok.Data;
import java.time.ZonedDateTime;

@Data
public class ApiException {

private String errorMessage;
private Integer statusCode;
private ZonedDateTime zonedDateTime;
}
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.time.ZonedDateTime;

@ControllerAdvice
public class ApiExceptionHandler {

@ExceptionHandler(UserNotFoundException.class)
@ResponseBody
public ApiException handleUserNotFoundException(UserNotFoundException e) {
ApiException exception = new ApiException();
exception.setErrorMessage(e.getMessage());
exception.setStatusCode(HttpStatus.NOT_FOUND.value());
exception.setZonedDateTime(ZonedDateTime.now());
return exception;
}
}
import lombok.Data;

@Data
public class UserNotFoundException extends RuntimeException {

private final String message;
public UserNotFoundException(String message) {
super(message);
this.message = message;
}
}

✅ Use structured logging

  • Structured logging is a method of generating log output in a consistent, machine-readable format, typically as key-value pairs, facilitating easier parsing, querying, and analysis by log management tools.
  • Using this simple property, you can enable it. The ecs means Elastic Common Schema format. There are other formats like gelf (Graylog Extended Log Format) and logstash.
logging:
structured:
format:
console: ecs

The sample output will look like this

{
"@timestamp": "2025-04-30T10:32:45.795203Z",
"log.level": "INFO",
"process.pid": 83487,
"process.thread.name": "http-nio-8080-exec-1",
"service.name": "demo",
"log.logger": "com.app.demo.client.controller.ClientController",
"message": "INFO",
"ecs.version": "8.11"
}

✅ Do not add business logic in controllers

  • When you develop a Spring Boot backend application, there are some important layers you annotate. @RestController @Service and @Repository.
  • Each one has a different perspective.
  • Controllers are dedicated to routing, not to handling business logic. It is a representation layer that handles HTTP requests and responses.
  • Always try to make your application with Clean Architecture.

✅ Use virtual threads

  • Virtual threads, introduced in JDK 21 as part of Project Loom, are lightweight threads managed by the JVM rather than the OS, unlike traditional platform threads that map directly to native threads.
  • Spring Boot 3.2+ provides integration with virtual threads via virtual thread executors, enabling developers to configure them seamlessly.
spring.threads.virtual.enabled=true
  • However, if you use a previous version of JDK other than 21, you may require additional configurations.
  • Please note that virtual threads are suitable for blocking scenarios like IO operations (file reading, writing, API calling, DB connections), and you will not get any benefit in CPU-intensive operations.

✅ Use Native image support with GraalVM

Press enter or click to view image in full size
  • This enables developers to compile Spring Boot applications into native executables, with faster startup times and reduced memory usage.
  • GraalVM is a high-performance runtime that compiles Java applications ahead of time into native executables.
  • By analyzing the application’s code and dependencies, it removes unused portions and optimizes the rest for the target platform.
  • The resulting standalone executable runs without a JVM, offering faster startup and lower resource consumption.

If you are using Maven, you can make your pom.xml like mentioned below.

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.1</version>
</parent>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>

Now you can build the image using,

mvn -Pnative spring-boot:build-image

If you use this, you may have some advantages.

  • Startup time will be reduced. Then the scaling will be quick.
  • Performance will be improved.
  • Uses lower memory, which is cost-effective.

✅ Limit the Actuator endpoint in production

  • Enabling the actuator endpoint in production may cause performance issues as well as security concerns.
  • Actuator endpoints may consume a significant amount of memory and CPU usage. Some endpoints may also increase the network traffic. (Eg: /actuator/metrics)
  • So it is better to disable them in production. You can change the configurations in the property file.

Enjoyed this article? If you found this helpful, consider supporting my work by buying me a coffee! Your support helps me create more content like this. 🚀☕

👉 Buy Me a Coffee ☕☕☕

Spring Boot Best Practices for New Developers 2026 Spring Boot Best Practices for New Developers 2026 Reviewed by Ravi Yasas on 1:04 PM Rating: 5

No comments:

Powered by Blogger.