Spring Boot — fundamentals
Spring Boot is the standard for building Java services at ProfessNet. It provides a ready-made DI container, an application server and autoconfiguration. This lesson shows the basic patterns: layers, controllers, injection and configuration.
Layers
We split a Spring Boot service into layers with clear responsibilities:
controller → service → repository → database
(HTTP) (logic) (data access)
ProfessNet standard: the controller contains no domain logic — it validates input, calls the
serviceand returns a response. Logic lives in@Service, data access in@Repository.
REST controller
@RestController
@RequestMapping("/api/v1/inventory")
public class InventoryController {
private final InventoryService service;
public InventoryController(InventoryService service) {
this.service = service;
}
@PostMapping("/scan")
@ResponseStatus(HttpStatus.ACCEPTED)
public ScanResult scan(@Valid @RequestBody ScanRequest request) {
return service.runScan(request.forest());
}
}
Constructor injection
We inject dependencies through the constructor and make fields final. We avoid
@Autowired on fields — it makes testing harder and hides dependencies.
// good — constructor injection, final fields
@Service
public class InventoryService {
private final ProbeRepository repository;
public InventoryService(ProbeRepository repository) {
this.repository = repository;
}
}
// bad — field injection
@Service
public class BadService {
@Autowired private ProbeRepository repository; // hard to test
}
ProfessNet standard: constructor injection always. Since version 4.3 Spring injects automatically when a class has a single constructor —
@Autowiredis not needed.
Input validation
We validate DTOs with Bean Validation annotations. @Valid in the controller
triggers validation, and errors become a readable 400.
public record ScanRequest(
@NotBlank @Size(max = 255) String forest,
boolean deep) {}
DTO as a record
To carry data we use record — immutable, concise, no boilerplate.
public record ScanResult(String jobId, String forest, Instant enqueuedAt) {}
Configuration
We keep settings in application.yml, and we inject secrets from environment
variables — we never hardcode them in the code.
spring:
datasource:
url: ${DB_URL}
username: ${DB_USER}
password: ${DB_PASSWORD}
zeus:
scan:
max-concurrency: 10
We inject typed properties via @ConfigurationProperties:
@ConfigurationProperties(prefix = "zeus.scan")
public record ScanProperties(int maxConcurrency) {}
| Annotation | Role |
|---|---|
@RestController | the HTTP layer, returns JSON |
@Service | domain logic |
@Repository | data access |
@Valid + Bean Validation | DTO validation |
@ConfigurationProperties | typed configuration |
Tip: secrets (
DB_PASSWORD) always via${ENV}. In the repo we keepapplication.ymlwithout secret values — those are supplied by the environment.
A clean split into layers, constructor injection, DTO validation and configuration outside the code — that's the foundation of every Spring Boot service at ProfessNet. On this base we build exception handling, logging and tests.