– Guard4j events describe what happens in your domain, not how you measure it.
Production-grade error handling and observability for Java applications. Framework-agnostic core with seamless Spring Boot, Quarkus, and Micronaut integration.
Enterprise Java applications typically suffer from inconsistent error handling that breaks down in production:
- Generic
Map<String, Object>error responses that clients can't reliably parse - Framework-specific error handling that doesn't work when you switch frameworks
- No built-in observability - errors disappear into logs without metrics or alerting
- Testing nightmares where MockMvc tests pass but production servlet errors fail
Guard4j provides type-safe error handling with automatic observability that works identically across all major Java frameworks:
// Clean, fluent error creation with business contextthrownewAppException(ErrorCodes.BUSINESS_RULE_VIOLATION)
.withData("rule", "DAILY_TRANSFER_LIMIT")
.withData("limit", 10000)
.withData("attempted", 15000);Emitter Factory Pattern
publicclassPaymentService {
privatestaticfinalEmitterevents = Guard4j.getEmitter(PaymentService.class);
publicvoidprocessPayment(Paymentpayment) {
// Single event call generates both metrics and structured logsevents.info(newPaymentProcessedEvent(payment.getId(), payment.getAmount()));
}
}{
"timestamp": "2024-01-15T10:30:00Z",
"status": 422,
"error": "Unprocessable Entity",
"code": "BUSINESS_RULE_VIOLATION",
"data": {
"userId": "user123",
"rule": "DAILY_TRANSFER_LIMIT",
"limit": 10000,
"attempted": 15000,
"retryable": false,
"severity": "medium"
}
}- Type-safe error responses - No more
Map<String, Object>- structured, predictable JSON - Framework-agnostic - Same error handling code works in Spring Boot, Quarkus, and Micronaut
- Automatic observability - Built-in metrics, structured logging, and alerting integration
- Production-ready - Configurable alert levels, retry logic, and environment-specific behavior
- Zero-config setup - Add dependency, start throwing better exceptions
<dependency>
<groupId>de.ferderer.guard4j</groupId>
<artifactId>guard4j-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>@RestControllerpublicclassTransferController {
privatestaticfinalEmitterevents = Guard4j.getEmitter(TransferController.class);
@PostMapping("/transfers")
publicTransferResulttransfer(@RequestBodyTransferRequestrequest) {
// Business rule validation with contextif (account.getBalance() < request.getAmount()) {
// Exception handlingthrownewAppException(ErrorCodes.BUSINESS_RULE_VIOLATION)
.withData("rule", "INSUFFICIENT_FUNDS")
.withData("balance", account.getBalance())
.withData("requested", request.getAmount());
}
// Business event observability - generates both metrics and logsevents.info(newTransferInitiatedEvent(request.getUserId(), request.getAmount()));
TransferResultresult = transferService.execute(request);
events.info(newTransferCompletedEvent(result.getTransferId(), result.getStatus()));
returnresult;
}
}
// Simple event definitionpublicrecordTransferInitiatedEvent(StringuserId, BigDecimalamount) implementsObservableEvent {}
publicrecordTransferCompletedEvent(StringtransferId, Stringstatus) implementsObservableEvent {}Guard4j automatically provides:
- Structured error responses that clients can reliably parse
- Micrometer metrics for error rates, categories, and business impact
- Structured logging with correlation IDs and business context
- Framework exception mapping - Spring validation errors become consistent ErrorResponse objects
| Framework | Status | Artifact |
|---|---|---|
| Spring Boot 3.x | ✅ Production Ready | guard4j-spring-boot-starter |
| Quarkus 3.x | 🛠️ In Active Development | guard4j-quarkus |
| Micronaut 4.x | 🚧 Coming Soon | guard4j-micronaut |
Create domain-specific error codes for your business logic:
publicenumPaymentErrorCodesimplementsErrorCode {
PAYMENT_GATEWAY_UNAVAILABLE(
HttpStatus.SERVICE_UNAVAILABLE,
Message.of("payment.gateway.unavailable", "Payment gateway temporarily unavailable"),
Severity.CRITICAL,
Category.EXTERNAL
),
DAILY_LIMIT_EXCEEDED(
HttpStatus.UNPROCESSABLE_ENTITY,
Message.of("payment.daily_limit", "Daily payment limit exceeded"),
Severity.WARN,
Category.BUSINESS
);
// Standard ErrorCode implementation...
}
// Usage with business eventspublicclassPaymentService {
privatestaticfinalEmitterevents = Guard4j.getEmitter(PaymentService.class);
publicvoidprocessPayment(Paymentpayment) {
if (exceedsDailyLimit(payment)) {
events.warn(newPaymentLimitExceededEvent(payment.getUserId(), payment.getAmount()));
thrownewAppException(PaymentErrorCodes.DAILY_LIMIT_EXCEEDED)
.withUserId(payment.getUserId())
.withData("amount", payment.getAmount())
.withData("dailyLimit", getDailyLimit(payment.getUserId()));
}
events.info(newPaymentProcessedEvent(payment.getId(), payment.getAmount()));
}
}Guard4j's Emitter Factory pattern provides unified observability for business events:
publicclassLoanProcessorService {
privatestaticfinalEmitterevents = Guard4j.getEmitter(LoanProcessorService.class);
publicvoidprocessLoan(LoanApplicationloan) {
// Replace verbose MeterRegistry + Logger calls with single eventevents.info(newLoanProcessingStartedEvent(loan.getId(), loan.getType()));
try {
LoanDecisiondecision = evaluateRules(loan);
// Business event generates automatic metrics + structured logsevents.info(newLoanProcessedEvent(loan, decision));
} catch (Exceptionex) {
events.error(newLoanProcessingFailedEvent(loan, ex));
throwex;
}
}
}
// Simple event definitionspublicrecordLoanProcessingStartedEvent(StringloanId, StringloanType) implementsObservableEvent {}
publicrecordLoanProcessedEvent(StringloanId, BigDecimalamount, Stringdecision, longprocessingTimeMs)
implementsObservableEvent {
@Overridepublicintmetric() {
return"APPROVED".equals(decision) ? 1 : 0; // Success rate tracking
}
}Automatic Output:
- Metrics:
guard4j_loan_processed_total{decision="approved", loan_type="mortgage"} - Logs:
{"level":"INFO","logger":"com.company.LoanProcessorService","event_type":"loan-processed","loan_id":"12345",...}
Guard4j follows the Emitter Factory Pattern for clean, type-safe observability:
// Get emitter for your class (cached, thread-safe)privatestaticfinalEmitterevents = Guard4j.getEmitter(MyService.class);
// Emit events with appropriate log levelsevents.info(newBusinessEvent(...)); // Business metrics + INFO logsevents.warn(newWarningEvent(...)); // Alert metrics + WARN logsevents.error(newErrorEvent(...)); // Error metrics + ERROR logsKey Benefits:
- Single Event Definition: One record generates both metrics and structured logs
- Type Safety: Compile-time validation of event structure
- Logger Correlation: Events use your class logger name for perfect correlation
- Framework Agnostic: Same API works across Spring Boot, Quarkus, Micronaut
- Java 17+
- Spring Boot 3.0+ / Quarkus 3.0+ / Micronaut 4.0+
- Getting Started Guide
- Framework-Specific Setup
- Custom Error Codes
- Production Configuration
- API Reference
Complete working examples for all supported frameworks:
- FinStream Trading API - Spring Boot 🛠️ In Active Development
- FinStream Trading API - Quarkus 🚧 Coming Soon
- FinStream Trading API - Micronaut 🚧 Coming Soon
- Quarkus Extension development for ReTrust production deployment
- Finalize Quarkus extension with native compilation support
- Community feedback and improvements
Apache License 2.0 - see LICENSE for details.
Contributions welcome! See CONTRIBUTING.md for guidelines.
Why Guard4j? As an independent consultant since 2001 (Java/Spring since 2009), I've seen the same error handling mistakes in enterprise projects across many companies. Guard4j provides the production-grade error handling and observability that Java applications actually need.