7 Essential Spring Boot 3 Practices Every Backend Developer Should Know
This article highlights seven often‑overlooked Spring Boot 3 details—ranging from dependency injection and controller design to configuration, constructor simplicity, environment profiles, exception handling, and response handling—providing concrete code examples and best‑practice recommendations to improve code quality and maintainability.
Introduction
This article examines seven easily overlooked details in Spring Boot 3 development that can cause traps, aiming to improve code quality, testability and maintainability.
Key Points
2.1 Avoid field injection with @Autowired/@Resource
Field injection creates tight coupling and hampers testing. Prefer constructor injection, optionally using Lombok's @RequiredArgsConstructor. Example:
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}Lombok is discouraged in this guide.
2.2 Keep business logic out of controllers
Controllers should only handle HTTP requests and delegate business logic to service layers, which simplifies unit testing and promotes reuse.
@RestController
@RequestMapping("/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
Product product = productService.getProductById(id);
return ResponseEntity.ok(product);
}
}2.3 Replace @Value with @ConfigurationProperties
@Value lacks structure and spreads configuration throughout the code. Define a dedicated configuration class annotated with @ConfigurationProperties to bind related properties.
@ConfigurationProperties(prefix = "pack.app")
public class AppConfig {
private String title;
private String version;
private Integer uid;
// getters and setters
}2.4 Keep constructors simple
Move heavy initialization out of constructors, using @PostConstruct methods or factory patterns.
public class CommonComponent {
private final CommonService commonService;
public CommonComponent(CommonService commonService) {
this.commonService = commonService;
}
@PostConstruct
public void init() {
// TODO
}
}2.5 Use separate configuration files for different environments
Define application-{profile}.properties or .yml files and activate the appropriate profile.
spring:
profiles:
active:
- dev2.6 Prefer exceptions over return‑value error codes
Throw a specific exception instead of returning an error wrapper, and handle it globally with @RestControllerAdvice.
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public Product queryById(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new ProductNotFoundException("Product not found, id: " + id));
}
}2.7 Use ResponseEntity as the response type
ResponseEntity provides flexible control over status codes, headers and body, and supports generics. When custom response objects are needed, they can still be used.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
