Exceptions and logging (SLF4J)
Good exception handling and thoughtful logging determine how quickly we diagnose problems in production. This lesson gathers the ProfessNet house rules for Java: custom exceptions, global handling and logging via SLF4J.
Catch specific exceptions
We don't catch a bare Exception, and we don't swallow errors. We catch a
specific type and handle it deliberately.
// bad — swallowed exception, the error disappears
try {
scanner.scan(forest);
} catch (Exception e) {
// silence
}
// good — a specific type, a log with context, a sensible reaction
try {
scanner.scan(forest);
} catch (ProbeTimeoutException e) {
log.warn("Timeout while scanning forest {}", forest, e);
throw new ScanFailedException("Probe did not respond", e);
}
ProfessNet standard: never an empty
catch. We either handle the exception, or wrap and propagate it while preserving thecause.
Custom domain exceptions
We create an exception hierarchy that mirrors the domain. We usually inherit
from RuntimeException (unchecked) so as not to clutter signatures.
public class ScanFailedException extends RuntimeException {
public ScanFailedException(String message, Throwable cause) {
super(message, cause);
}
}
Global handling in Spring
In Spring Boot we map exceptions to HTTP responses centrally via
@RestControllerAdvice — the controllers stay clean.
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log =
LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(ProbeNotFoundException.class)
public ResponseEntity<ApiError> handleNotFound(ProbeNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ApiError("PROBE_NOT_FOUND", e.getMessage()));
}
@ExceptionHandler(ScanFailedException.class)
public ResponseEntity<ApiError> handleScanFailed(ScanFailedException e) {
log.error("Scan failed", e);
return ResponseEntity.status(HttpStatus.BAD_GATEWAY)
.body(new ApiError("SCAN_FAILED", "The operation failed"));
}
}
Logging via SLF4J
We log through the SLF4J facade (LoggerFactory.getLogger). We use
parameterized logging ({}) — efficient and safe.
private static final Logger log = LoggerFactory.getLogger(InventoryService.class);
// good — parameters, lazy evaluation
log.info("Started scanning forest {} ({} hosts)", forest, hostCount);
// bad — string concatenation (always builds the string, even when the log is disabled)
log.info("Started scanning forest " + forest);
ProfessNet standard: we log via SLF4J, always with
{}parameters, never via concatenation. We pass the exception as the last argument — then SLF4J logs the full stack trace.
Log levels
| Level | When |
|---|---|
ERROR | an error requiring attention (operation failure) |
WARN | an unusual but handled situation (timeout, retry) |
INFO | significant business events (scan start, result) |
DEBUG | diagnostic details (disabled in production) |
Tip: never log secrets or sensitive data — passwords, tokens, full personal data. Logs go to systems that have a different access circle than the database.
Specific exceptions, a custom domain hierarchy, a global handler and SLF4J logging with parameters — that's the recipe for a service that's easy to diagnose. Errors should be visible and readable, never hidden.