⚡ Hermes Logging Library for Java
High-performance logging library for Java with zero-boilerplate annotation processing, async logging via LMAX Disruptor, and comprehensive Spring Boot integration.
Ever found yourself writing private static final Logger log = LoggerFactory.getLogger(MyClass.class); for the hundredth time and thinking, "There has to be a better way"? Or maybe you've watched your high-throughput Java app grind to a halt because logging became a bottleneck? Yeah, we've been there too.
Hermes is a modern logging library for Java 17+ that ditches the old baggage. No more boilerplate logger declarations. No more XML config files. No more lock contention killing your throughput. Instead, you get compile-time code generation, zero-allocation message formatting, and lock-free async processing via LMAX Disruptor. Named after the Greek messenger god who delivered messages at lightning speed, Hermes does exactly that for your logs.
Why Hermes?
Look, most logging frameworks were built back in the Java 6 days. They work, sure, but they weren't designed for today's cloud-native, high-throughput world. If you're running microservices that handle thousands of requests per second, or processing millions of Kafka messages per hour, traditional logging can become your bottleneck.
The Logging Performance Problem
Think about what happens every time you log something: format a message, acquire a lock, allocate strings, write to disk. Multiply that by a million events per second. Suddenly, those microseconds add up. Here's what goes wrong with traditional approaches:
- Reflection overhead for logger field injection
- Lock contention in multi-threaded async appenders
- GC pressure from excessive string allocation during message formatting
- Thread blocking when writing to I/O streams synchronously
- Configuration complexity with XML or properties files
Zero-Boilerplate Development
Okay, let's talk about the elephant in the room: that logger declaration you copy-paste into every single class. You know the one. And you know how easy it is to forget to change the class name, right? We've all been there.
Hermes fixes this with one simple annotation:
@InjectLogger
public class UserService extends UserServiceHermesLogger {
public void createUser(String username) {
log.info("Creating user: {}", username);
}
}
That's it. The @InjectLogger annotation triggers compile-time code generation that creates a base class with a protected Logger log field ready to use. What's cool about this?
- No boilerplate — Zero. Nada. And no runtime reflection overhead either.
- Type-safe — Catches errors at compile time, not at 3 AM in production.
- GraalVM-friendly — Works with native images out of the box.
- IDE-friendly — Your IDE's autocomplete just works.
Production-Ready Features
Speed is great, but you also need this thing to actually work in production, right? Hermes has you covered:
- Structured JSON logging — Works seamlessly with ELK, Splunk, Datadog, and friends.
- MDC support — Track requests across your distributed system.
- Multiple appenders — Console, File, RollingFile, Async, Logstash—take your pick.
- Flexible formatting — Customize patterns however you like.
- Spring Boot integration — Auto-configuration via YAML. No XML nightmares.
- Kotlin DSL — Because Kotlin developers deserve nice things too.
- GraalVM support — Native images? Yeah, that works.
Core Architecture
Hermes uses a modular architecture split into six focused modules. You only include what you need, and the dependency boundaries are crystal clear.
System Architecture Overview
Module Dependency Structure
Hermes is organized into six distinct modules, each with specific responsibilities:
Module Breakdown
1. hermes-api - The Public Contract
Contains all public interfaces, annotations, and API surface that applications compile against. This separation enables implementation swapping without recompilation.
Key components:
Loggerinterface with level-specific methods (trace, debug, info, warn, error)LoggerFactoryfor logger instantiation@InjectLoggerannotation for compile-time injectionLogLevelenum (TRACE, DEBUG, INFO, WARN, ERROR)MDC(Mapped Diagnostic Context) for thread-local contextual dataMarkerinterface for log categorization
2. hermes-core - The Implementation Engine
Provides the concrete implementation of all logging functionality, optimized for performance and zero-allocation where possible.
Key components:
HermesLogger- Main logger implementation with level checksLogEvent- Immutable Java 17 record capturing log dataMessageFormatter- Zero-allocation message formatting using ThreadLocal StringBuilder- Appenders: Console, File, RollingFile, Async (with Disruptor), Logstash
- Layouts: Pattern (customizable format strings), JSON (structured logging)
- Performance optimizations: early exit, ThreadLocal reuse, lock-free data structures
3. hermes-processor - Compile-Time Code Generation
Annotation processor that generates logger base classes during compilation, eliminating runtime reflection overhead.
How it works:
- Detects
@InjectLoggerannotation during compilation - Generates a base class (e.g.,
UserServiceHermesLogger) withprotected Logger logfield - User class extends the generated base class to access the logger
- No runtime overhead—all code generation happens at compile time
4. hermes-spring-boot-starter - Spring Boot Integration
Auto-configuration module providing seamless Spring Boot integration with property binding and lifecycle management.
Features:
@EnableAutoConfigurationsupportHermesPropertiesfor YAML/properties binding- Actuator health indicator for monitoring
- Automatic logger configuration from
application.yml
5. hermes-kotlin - Idiomatic Kotlin Extensions
Kotlin DSL providing idiomatic syntax for Kotlin projects using extension functions and builders.
Features:
MyClass::class.logger- Extension property for logger creation- Lazy evaluation:
log.info { "Expensive $computation" } - MDC DSL:
withMDC("key" to "value") { ... } - Structured logging builders with type-safe key-value pairs
6. hermes-examples - Reference Implementations
Working examples demonstrating various Hermes features and integration patterns.
High-Performance Async Logging with LMAX Disruptor
Here's the thing: most async logging uses lock-based queues. They work okay for moderate loads, but when you're pushing millions of logs per second, those locks become a serious bottleneck. Thread contention, context switching—it all adds up.
Hermes uses the LMAX Disruptor instead. If you haven't heard of it, it's the same tech that powers financial trading systems where nanoseconds matter. It's lock-free, wait-free, and absurdly fast.
Async Logging Flow
The asynchronous logging process is designed to minimize application thread latency while maximizing throughput:
Performance Characteristics
Application Thread (Publisher)
- Latency: ~100 nanoseconds (wait-free publish)
- Non-blocking: Returns immediately after publishing to ring buffer
- No locks: Uses CAS (Compare-And-Swap) operations
Background Thread (Consumer)
- Batching: Processes multiple events together, amortizing overhead
- Sequential I/O: Writes are coalesced for better disk throughput
- Single consumer: Eliminates coordination overhead
Throughput Comparison
-
ArrayBlockingQueue(lock-based)- Throughput: ~1M logs/sec
- Latency: ~5-10 µs
-
LMAX Disruptor (lock-free)
- Throughput: 10M+ logs/sec
- Latency: ~0.1 µs
Ring Buffer Mechanics
The ring buffer is pre-allocated with a fixed size (power of 2, typically 1024-8192 slots). This design provides several advantages:
- Memory locality: Contiguous memory improves CPU cache utilization
- No allocation: Slots are reused, eliminating GC pressure
- Fast modulo: Ring wrapping uses bitwise AND instead of expensive modulo operation
- Wait-free: Publishers never wait for consumers (unless buffer full)
Zero-Allocation Message Formatting
Every time traditional logging formats a message, it creates new String objects. One log message? No big deal. A million per second? Now you've got a garbage collection nightmare.
Hermes fixes this by reusing ThreadLocal StringBuilder instances. After the initial allocation, it's zero-copy formatting all the way down.
Performance Impact
Before (Traditional Approach)
// Creates 3 new String objects per call
log.info("User " + username + " logged in at " + timestamp);
- Allocations: 3+ String objects, intermediate char arrays
- GC pressure: Continuous young generation collections
- Throughput impact: GC pauses affect latency
After (Hermes Zero-Allocation)
// Zero allocations using ThreadLocal StringBuilder reuse
log.info("User {} logged in at {}", username, timestamp);
- Allocations: Zero (after first call per thread)
- GC pressure: Minimal—only final String and LogEvent allocated
- Throughput: No GC pauses from message formatting
Implementation Details
public class MessageFormatter {
// ThreadLocal ensures each thread has its own StringBuilder
private static final ThreadLocal<StringBuilder> BUFFER =
ThreadLocal.withInitial(() -> new StringBuilder(256));
public static String format(String pattern, Object... args) {
StringBuilder sb = BUFFER.get();
sb.setLength(0); // Clear but don't deallocate
int argIndex = 0;
int patternLength = pattern.length();
for (int i = 0; i < patternLength; i++) {
char c = pattern.charAt(i);
if (c == '{' && i + 1 < patternLength && pattern.charAt(i + 1) == '}') {
// Placeholder found
if (argIndex < args.length) {
sb.append(args[argIndex++]);
}
i++; // Skip the '}'
} else {
sb.append(c);
}
}
return sb.toString(); // Only allocation
}
}
Spring Boot Integration
If you're using Spring Boot (and let's be honest, you probably are), Hermes plays nice right out of the box. No complex setup, no XML files to wrestle with—just add the starter and configure via YAML.
Configuration Example
# application.yml
hermes:
# Root log level and package-specific levels
level:
root: INFO
packages:
io.github.dotbrains: DEBUG
com.myapp.service: TRACE
com.myapp.repository: DEBUG
# Log output pattern (supports multiple format directives)
pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
# Async logging configuration
async:
enabled: true
queue-size: 2048 # Ring buffer size (must be power of 2)
Pattern Format Directives
%d{pattern}- Timestamp (e.g.,2026-01-10 11:45:30.123)%thread- Thread name (e.g.,http-nio-8080-exec-1)%level- Log level (e.g.,INFO)%-5level- Left-aligned level, 5 chars (e.g.,INFO)%logger{length}- Logger name, abbreviated (e.g.,c.e.s.UserService)%msg- Log message (e.g.,User created successfully)%n- Line separator (platform-specific)%mdc{key}- MDC value (e.g.,req-12345)%exception- Exception stack trace (full stack trace)
Structured JSON Logging
If you're shipping logs to ELK, Splunk, Datadog, or any modern observability platform, you need JSON. Not because it's trendy, but because it's the only way to make your logs actually queryable and useful.
Hermes has built-in JSON formatting that's optimized for these platforms:
JSON Output Example
{
"timestamp": "2026-01-10T04:58:51.123Z",
"level": "INFO",
"logger": "com.example.UserService",
"thread": "http-nio-8080-exec-1",
"threadId": "42",
"message": "User john.doe created successfully",
"mdc": {
"requestId": "req-789",
"userId": "12345",
"sessionId": "sess-abc"
}
}
Logstash Integration
Hermes includes a dedicated LogstashAppender for direct TCP streaming to Logstash:
LogstashAppender logstash = new LogstashAppender(
"logstash",
"logstash.example.com", // Logstash host
5000 // TCP input port
);
logstash.start();
Logstash Configuration:
input {
tcp {
port => 5000
codec => json
}
}
filter {
# Add additional fields, parse timestamps, etc.
mutate {
add_field => { "environment" => "production" }
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "hermes-logs-%{+YYYY.MM.dd}"
}
}
Kotlin DSL
Writing Kotlin? You shouldn't have to suffer through a Java logging API. Hermes includes a dedicated Kotlin module with extension functions and DSL builders that feel natural in Kotlin code.
Kotlin Extensions Overview
import io.github.dotbrains.kotlin.*
class UserService {
// Extension property for logger creation
private val log = UserService::class.logger
fun createUser(userId: String, username: String) {
// Lazy evaluation - only computed if DEBUG enabled
log.debug { "Creating user with expensive data: ${expensiveOperation()}" }
// MDC with automatic cleanup
withMDC("userId" to userId, "username" to username) {
log.info { "Processing user creation" }
// MDC is automatically cleared after block
}
// Structured logging DSL
log.infoWith {
"message" to "User created successfully"
"userId" to userId
"username" to username
"timestamp" to System.currentTimeMillis()
"environment" to System.getenv("ENV")
}
}
// Exception logging with Kotlin syntax
fun handleError(userId: String, error: Exception) {
log.error(error) { "Failed to process user: $userId" }
}
}
Lazy Evaluation Benefits
Kotlin's lambda syntax enables true lazy evaluation—expensive operations are only executed if the log level is enabled:
// ❌ BAD: expensiveOperation() always called, even if DEBUG disabled
log.debug("Result: ${expensiveOperation()}")
// ✅ GOOD: expensiveOperation() only called if DEBUG enabled
log.debug { "Result: ${expensiveOperation()}" }
GraalVM Native Image Support
Hermes includes GraalVM native-image metadata for ahead-of-time compilation to native executables, eliminating the need for manual reflection configuration.
Native Image Metadata
Hermes includes the following metadata in hermes-core/src/main/resources/META-INF/native-image/:
reflect-config.json - Reflection hints
[
{
"name": "io.github.dotbrains.core.HermesLoggerProvider",
"allPublicMethods": true
}
]
resource-config.json - Resource patterns
{
"resources": {
"includes": [
{ "pattern": "META-INF/services/.*" },
{ "pattern": "application.ya?ml" }
]
}
}
Building Native Images
# Maven with GraalVM native plugin
mvn -Pnative native:compile
# Gradle with GraalVM native plugin
./gradlew nativeCompile
# Direct native-image invocation
native-image --no-fallback -jar myapp.jar
Performance Benchmarks and Metrics
Hermes is designed for high-performance scenarios. Here are the expected performance characteristics:
Latency Measurements
- Disabled log statement: ~1 ns (level check only, early exit)
- Enabled sync log (console): ~1-5 µs (message formatting + I/O)
- Enabled async log (Disruptor): ~100 ns (wait-free publish to ring buffer)
- JSON formatting: ~2-10 µs (object construction + serialization)
- File write (buffered): ~5-20 µs (depends on I/O buffer size)
Throughput Comparison
- Sync console logging: ~1M logs/sec (single thread, pattern layout)
- Async file logging: ~10M+ logs/sec (multiple threads, Disruptor, buffered I/O)
- JSON structured logging: ~500K logs/sec (single thread, JSON layout)
Memory Efficiency
Before (Traditional Logging)
- String allocation per log message
- Intermediate object creation during formatting
- GC pressure from short-lived objects
After (Hermes Optimization)
- ThreadLocal StringBuilder reuse (zero allocation after warmup)
- Immutable LogEvent records (compact memory layout)
- Pre-allocated ring buffer (no allocation during logging)
Use Cases and Deployment Scenarios
Hermes excels in various production scenarios where logging performance and developer experience matter:
High-Throughput Microservices
Scenario: REST API handling 10K+ requests/second with detailed request tracing.
Solution:
- Async appender with Disruptor for non-blocking logging
- MDC for request correlation (
requestId,userId,sessionId) - JSON layout for structured logs sent to ELK stack
Benefits:
- Application threads return immediately (sub-microsecond logging)
- Request tracing across distributed services via MDC propagation
- Efficient log querying in Elasticsearch using structured fields
Data Processing Pipelines
Scenario: Apache Kafka consumer processing millions of messages per hour with detailed progress tracking.
Solution:
- Rolling file appender with size-based rotation
- Zero-allocation message formatting to minimize GC pauses
- Conditional debug logging for problematic message inspection
Benefits:
- Minimal overhead on message processing throughput
- Automatic log rotation prevents disk exhaustion
- Debug logging enabled dynamically without restarts
Spring Boot Enterprise Applications
Scenario: Monolithic Spring Boot application with complex business logic requiring comprehensive audit logging.
Solution:
- Spring Boot starter for auto-configuration
- YAML-based configuration for environment-specific settings
- Multiple appenders (console for dev, file + Logstash for production)
- MDC populated via Spring interceptors for request tracking
Benefits:
- Zero-boilerplate logger injection with
@InjectLogger - Environment-specific configuration without code changes
- Seamless integration with Spring Boot Actuator
Kotlin-Based Applications
Scenario: Modern Kotlin microservice requiring idiomatic logging syntax.
Solution:
- Kotlin DSL with lazy evaluation and structured logging builders
- Extension functions for logger creation
- MDC DSL for contextual data
Benefits:
- Lazy evaluation prevents unnecessary computation
- Type-safe structured logging with DSL builders
- Cleaner code with Kotlin-idiomatic syntax
Technical Highlights
What Sets Hermes Apart
1. Compile-Time Logger Injection
- Zero runtime overhead through annotation processing
- Type-safe generated base classes
- GraalVM-friendly (no reflection configuration)
2. Lock-Free Async Processing
- LMAX Disruptor for 10-100x better throughput than lock-based queues
- Wait-free publish semantics
- Single consumer pattern eliminates coordination overhead
3. Zero-Allocation Formatting
- ThreadLocal StringBuilder reuse
- Early exit for disabled log levels
- Minimal GC pressure
4. Modern Java Features
- Java 17 records for immutable, compact LogEvent
- ServiceLoader pattern for decoupled architecture
- Text blocks for readable configuration
5. Comprehensive Integration
- Spring Boot auto-configuration
- Kotlin DSL extensions
- GraalVM Native Image metadata
- Logstash direct streaming
How Does Hermes Stack Up?
You're probably wondering: "How does this compare to what I'm already using?" Fair question. Here's the honest breakdown:
| Feature | SLF4J + Logback | Log4j2 | Hermes |
|---|---|---|---|
| Logger Injection | Manual or Lombok | Manual | @InjectLogger annotation |
| Configuration | XML/Groovy | XML/JSON/YAML | YAML with defaults |
| Async Logging | Via appenders (lock-based) | Lock-free (Disruptor) | Built-in LMAX Disruptor |
| Message Formatting | String concatenation | ThreadLocal reuse | Zero-allocation |
| Spring Boot Integration | External starters | External starters | Native auto-config |
| Kotlin Support | Java API only | Java API only | Dedicated Kotlin DSL |
| GraalVM Native Image | Manual configuration | Manual configuration | Included metadata |
| Java Version | 8+ | 8+ | 17+ (modern features) |
| JSON Logging | Via Jackson/Gson | Built-in | Optimized built-in |
Getting Started
Ready to give it a spin? Here's how to get Hermes into your project.
Maven Setup
<dependencies>
<!-- Core API -->
<dependency>
<groupId>io.github.dotbrains</groupId>
<artifactId>hermes-api</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Annotation processor (for @InjectLogger) -->
<dependency>
<groupId>io.github.dotbrains</groupId>
<artifactId>hermes-processor</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>
<!-- Core implementation -->
<dependency>
<groupId>io.github.dotbrains</groupId>
<artifactId>hermes-core</artifactId>
<version>1.0.0</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.github.dotbrains</groupId>
<artifactId>hermes-processor</artifactId>
<version>1.0.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
Spring Boot Quick Start
1. Add the starter dependency:
<dependency>
<groupId>io.github.dotbrains</groupId>
<artifactId>hermes-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
2. Configure in application.yml:
hermes:
level:
root: INFO
packages:
com.example: DEBUG
async:
enabled: true
queue-size: 2048
3. Use in your code:
@InjectLogger
@Service
public class UserService extends UserServiceHermesLogger {
public void createUser(String username) {
log.info("Creating user: {}", username);
}
}
Tech Stack
Core Technologies:
- Java 17 (Records, Text Blocks, Modern JVM)
- LMAX Disruptor (Lock-free async processing)
- ServiceLoader SPI (Decoupled architecture)
- Annotation Processing (Compile-time code generation)
Integration & Frameworks:
- Spring Boot (Auto-configuration)
- Kotlin (DSL extensions)
- Maven/Gradle (Build system)
- GraalVM (Native image compilation)
Testing & Quality:
- JUnit 5 (Unit testing)
- Mockito (Mocking framework)
- GitHub Actions (CI/CD)
- Maven Central (Artifact distribution)
The Bottom Line
Hermes brings Java logging into the modern era. By leveraging Java 17 features, LMAX Disruptor, and compile-time code generation, it delivers the performance you need without the boilerplate you hate.
Whether you're building microservices that need to log millions of events per second, data pipelines processing Kafka streams, or enterprise Spring Boot apps, Hermes has your back. Fast, flexible, and actually enjoyable to use—that's the goal.
Want to learn more? Check out the official docs or dive into the GitHub repo. And yes, contributions are welcome!