Java/03core13 min

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 service and 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 — @Autowired is 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) {}
AnnotationRole
@RestControllerthe HTTP layer, returns JSON
@Servicedomain logic
@Repositorydata access
@Valid + Bean ValidationDTO validation
@ConfigurationPropertiestyped configuration

Tip: secrets (DB_PASSWORD) always via ${ENV}. In the repo we keep application.yml without 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.