โ 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 context
throw new AppException(ErrorCodes.BUSINESS_RULE_VIOLATION)
.withData("rule", "DAILY_TRANSFER_LIMIT")
.withData("limit", 10000)
.withData("attempted", 15000);Emitter Factory Pattern
public class PaymentService {
private static final Emitter events = Guard4j.getEmitter(PaymentService.class);
public void processPayment(Payment payment) {
// Single event call generates both metrics and structured logs
events.info(new PaymentProcessedEvent(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>@RestController
public class TransferController {
private static final Emitter events = Guard4j.getEmitter(TransferController.class);
@PostMapping("/transfers")
public TransferResult transfer(@RequestBody TransferRequest request) {
// Business rule validation with context
if (account.getBalance() < request.getAmount()) {
// Exception handling
throw new AppException(ErrorCodes.BUSINESS_RULE_VIOLATION)
.withData("rule", "INSUFFICIENT_FUNDS")
.withData("balance", account.getBalance())
.withData("requested", request.getAmount());
}
// Business event observability - generates both metrics and logs
events.info(new TransferInitiatedEvent(request.getUserId(), request.getAmount()));
TransferResult result = transferService.execute(request);
events.info(new TransferCompletedEvent(result.getTransferId(), result.getStatus()));
return result;
}
}
// Simple event definition
public record TransferInitiatedEvent(String userId, BigDecimal amount) implements ObservableEvent {}
public record TransferCompletedEvent(String transferId, String status) implements ObservableEvent {}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:
public enum PaymentErrorCodes implements ErrorCode {
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 events
public class PaymentService {
private static final Emitter events = Guard4j.getEmitter(PaymentService.class);
public void processPayment(Payment payment) {
if (exceedsDailyLimit(payment)) {
events.warn(new PaymentLimitExceededEvent(payment.getUserId(), payment.getAmount()));
throw new AppException(PaymentErrorCodes.DAILY_LIMIT_EXCEEDED)
.withUserId(payment.getUserId())
.withData("amount", payment.getAmount())
.withData("dailyLimit", getDailyLimit(payment.getUserId()));
}
events.info(new PaymentProcessedEvent(payment.getId(), payment.getAmount()));
}
}Guard4j's Emitter Factory pattern provides unified observability for business events:
public class LoanProcessorService {
private static final Emitter events = Guard4j.getEmitter(LoanProcessorService.class);
public void processLoan(LoanApplication loan) {
// Replace verbose MeterRegistry + Logger calls with single event
events.info(new LoanProcessingStartedEvent(loan.getId(), loan.getType()));
try {
LoanDecision decision = evaluateRules(loan);
// Business event generates automatic metrics + structured logs
events.info(new LoanProcessedEvent(loan, decision));
} catch (Exception ex) {
events.error(new LoanProcessingFailedEvent(loan, ex));
throw ex;
}
}
}
// Simple event definitions
public record LoanProcessingStartedEvent(String loanId, String loanType) implements ObservableEvent {}
public record LoanProcessedEvent(String loanId, BigDecimal amount, String decision, long processingTimeMs)
implements ObservableEvent {
@Override
public int metric() {
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)
private static final Emitter events = Guard4j.getEmitter(MyService.class);
// Emit events with appropriate log levels
events.info(new BusinessEvent(...)); // Business metrics + INFO logs
events.warn(new WarningEvent(...)); // Alert metrics + WARN logs
events.error(new ErrorEvent(...)); // 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.