Flattened bundle. Content from source markdown guides is inlined below.
This guide provides step-by-step instructions for adding avaje-simple-logger to a Maven project, including how to replace Logback or other SLF4J implementations.
Before starting, verify the following:
- You have a Maven project with a
pom.xmlfile - Java 11 or later is the target version
- You have identified any existing logging framework (Logback, Log4j 2, etc.)
- You understand the difference between your
src/main/resourcesandsrc/test/resourcesdirectories
Run the following command to see your project's dependencies:
mvn dependency:tree | grep -E "(logback|log4j|slf4j|logger)"This will help you identify which scenario (Fresh Start, Replace Logback, or Replace Log4j) applies to your project.
Does your project have any logging framework?
- No logging framework → Go to: Option 1: Fresh Start
- Using Logback (ch.qos.logback) → Go to: Option 2: Replace Logback
- Using Log4j 2 (org.apache.logging.log4j) → Go to: Option 3: Replace-log4j-2
- Using other SLF4J implementation → Use Option 2 as reference, remove the other binding
When to use: Adding logging to a project with no existing logging framework.
Open your pom.xml and add the following dependency in the <dependencies> section:
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-simple-logger</artifactId>
<version>1.5-RC1</version>
</dependency>Create the file src/main/resources/avaje-logger.properties with the following content:
# Default log level for all loggers (TRACE, DEBUG, INFO, WARN, ERROR)
logger.defaultLogLevel=warn
# Output format: json (default) or plain
logger.format=json
# JSON field naming convention: underscore (default), camel, or legacy
logger.naming=underscore
# Logger name formatting: full, short, or character limit (e.g., 100)
logger.nameTargetLength=50
# Timezone for log timestamps (optional)
#logger.timezone=UTC
# Specific package log levels (optional)
log.level.io.avaje=INFO
log.level.com.mycompany=DEBUGCreate the file src/test/resources/avaje-logger-test.properties with the following content:
# For tests, use plain text format for readability
logger.format=plain
# Default log level for tests (usually more verbose than production)
logger.defaultLogLevel=INFO
# Test-specific package levels
log.level.io.avaje=DEBUG
log.level.com.mycompany=DEBUGRun the following command to verify everything is working:
mvn clean testYou should see logs appearing in the test output in plain text format. If you don't see any logs, verify:
- The properties files are in the correct directories
- The log level is not set too high (WARN or ERROR might not show test logs)
When to use: Your project currently uses Logback (ch.qos.logback).
In your pom.xml, find and remove these dependencies:
<!-- REMOVE THESE -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<!-- version ... -->
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<!-- version ... -->
</dependency>Also check for and remove any other logging bridges:
<!-- REMOVE if present -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>Add the avaje-simple-logger dependency to your pom.xml:
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-simple-logger</artifactId>
<version>1.5-RC1</version>
</dependency>Find and delete these files from your project:
src/main/resources/logback.xmlsrc/main/resources/logback-spring.xml(if using Spring Boot)src/test/resources/logback-test.xml
Review your old logback.xml file and map the configuration to avaje-logger.properties.
| Logback Setting | avaje-simple-logger Equivalent |
|---|---|
<root level="WARN"> |
logger.defaultLogLevel=warn |
<logger name="com.foo" level="DEBUG"> |
log.level.com.foo=DEBUG |
<pattern>%msg%n</pattern> |
logger.format=plain |
<pattern>%d %msg%n</pattern> |
logger.format=json (timestamps included) |
<appender ref="STDOUT"> |
(implicit - uses System.out) |
Old logback.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
<logger name="com.mycompany" level="DEBUG" />
<logger name="io.avaje" level="INFO" />
</configuration>New avaje-logger.properties:
logger.defaultLogLevel=warn
logger.format=json
log.level.com.mycompany=DEBUG
log.level.io.avaje=INFOCreate src/test/resources/avaje-logger-test.properties:
logger.format=plain
logger.defaultLogLevel=INFO
log.level.com.mycompany=DEBUG
log.level.io.avaje=DEBUGRun the following commands:
# Check that Logback is no longer a dependency
mvn dependency:tree | grep logback
# Build and test
mvn clean testYou should see:
- No logback entries in dependency tree
- Logs appearing in test output (in plain format)
- No "SLF4J: No providers found" errors
When to use: Your project currently uses Apache Log4j 2.
In your pom.xml, find and remove these dependencies:
<!-- REMOVE THESE -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<!-- version ... -->
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<!-- version ... -->
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<!-- version ... -->
</dependency>Also check for these and remove if present:
<!-- REMOVE if present -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
</dependency><dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-simple-logger</artifactId>
<version>1.5-RC1</version>
</dependency>Find and delete:
src/main/resources/log4j2.xmlsrc/main/resources/log4j2.propertiessrc/main/resources/log4j2.yamlsrc/test/resources/log4j2-test.xmlsrc/test/resources/log4j2-test.properties
Review your old log4j2.xml and map to avaje-logger.properties.
Old log4j2.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="warn">
<AppenderRef ref="Console"/>
</Root>
<Logger name="com.mycompany" level="debug"/>
<Logger name="io.avaje" level="info"/>
</Loggers>
</Configuration>New avaje-logger.properties:
logger.defaultLogLevel=warn
logger.format=plain
log.level.com.mycompany=debug
log.level.io.avaje=infoCreate src/test/resources/avaje-logger-test.properties:
logger.format=plain
logger.defaultLogLevel=INFO
log.level.com.mycompany=DEBUG
log.level.io.avaje=DEBUG# Check that Log4j 2 is no longer a dependency
mvn dependency:tree | grep log4j
# Build and test
mvn clean testUse this for projects that need dynamic log level configuration:
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-simple-logger</artifactId>
<version>1.5-RC1</version>
</dependency>This includes:
avaje-simple-json-logger- Core JSON loggingavaje-config- Dynamic configuration supportavaje-applogandavaje-applog-slf4j- Application logging bridge
If you don't need dynamic configuration, use this instead:
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-simple-json-logger</artifactId>
<version>1.5-RC1</version>
</dependency>Note: You can still change log levels programmatically via LoggerContext.get().putAll() if needed.
Make sure you only have ONE SLF4J binding. Remove any of these if present:
<!-- Remove any of these -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>Production: src/main/resources/avaje-logger.properties
This file is loaded automatically when the application starts.
The default log level for all loggers when no specific level is configured.
Values: TRACE, DEBUG, INFO, WARN, ERROR
logger.defaultLogLevel=warnOutput format for logs.
Values: json (default), plain
# JSON format (structured logs for log aggregation)
logger.format=json
# Plain text format (human readable)
logger.format=plainSet specific log levels for individual packages.
# Set com.mycompany package to DEBUG level
log.level.com.mycompany=DEBUG
# Set io.avaje package to INFO level
log.level.io.avaje=INFO
# Multiple settings
log.level.com.foo=WARN
log.level.com.foo.bar=DEBUGThe application component name (added to JSON output).
# Literal value
logger.component=my-service
# From environment variable or system property
logger.component=${SERVICE_NAME}
# From system property with fallback
logger.component=${app.name:my-service}The environment name (added to JSON output).
# Literal value
logger.environment=production
# From environment variable with fallback
logger.environment=${APP_ENV:local}How to format logger names in output.
# Use full logger name (default)
logger.nameTargetLength=full
# Use only the class name (last part after .)
logger.nameTargetLength=short
# Abbreviate to 100 characters (shortens package names)
logger.nameTargetLength=100Timezone for timestamps in logs.
# Use UTC timezone
logger.timezone=UTC
# Use system default timezone
logger.timezone=systemTimestamp format in logs.
Values: ISO_OFFSET_DATE_TIME (default), ISO_ZONED_DATE_TIME, ISO_LOCAL_DATE_TIME, ISO_DATE_TIME, ISO_INSTANT
logger.timestampPattern=ISO_OFFSET_DATE_TIMEJSON field naming convention (when using logger.format=json).
Values: underscore (default), camel, legacy
# Underscore format (default, recommended for new projects)
# Fields: logger_name, exception_type, exception_message, exception_stacktrace
logger.naming=underscore
# CamelCase format
# Fields: loggerName, exceptionType, exceptionMessage, exceptionStacktrace
logger.naming=camel
# Legacy format (for backwards compatibility)
# Fields: logger, exceptionType, exceptionMessage, stacktrace
logger.naming=legacyExample JSON output with underscore (default):
{
"logger_name":"io.avaje.config",
"exception_type":"java.lang.RuntimeException",
"exception_message":"Configuration error"
}Example JSON output with camelCase:
{
"loggerName":"io.avaje.config",
"exceptionType":"java.lang.RuntimeException",
"exceptionMessage":"Configuration error"
}Override specific JSON property names.
# Override individual property names (comma and equals delimited)
logger.propertyNames=logger_name=app_logger,env=application_env,timestamp=@timestamp
# Override a single property
logger.propertyNames=logger_name=loggerName# Basic configuration
logger.defaultLogLevel=warn
logger.format=json
logger.naming=underscore
logger.nameTargetLength=full
logger.timezone=UTC
# Application context
logger.component=${SERVICE_NAME:my-app}
logger.environment=${APP_ENV:development}
# Package-specific levels
log.level.com.mycompany=INFO
log.level.com.mycompany.sensitive=DEBUG
log.level.io.avaje=WARN
log.level.org.springframework=WARN
# Adjust for troubleshooting
# log.level.com.mycompany.payment=TRACETesting: src/test/resources/avaje-logger-test.properties
This file overrides the production config when running tests (Maven's test classpath has different resource priority).
# Plain format for readability during test execution
logger.format=plain
# Higher default log level to see test activity
logger.defaultLogLevel=INFO
# More verbose logging for specific packages
log.level.com.mycompany=DEBUG
log.level.com.mycompany.test=TRACE
log.level.io.avaje=DEBUG- You want different log levels during testing than production
- You prefer plain text format during test execution
- You need TRACE-level logging for specific packages during testing
- You want less verbose logging to reduce test output
If you only create avaje-logger.properties, the same settings will be used for both production and testing.
Ensure the project builds without errors:
mvn clean packageExpected output:
- ✅ No SLF4J warnings or errors
- ✅ Build completes successfully
- ✅ No "Multiple SLF4J bindings found" messages
Run the tests to verify logging is working:
mvn testExpected output:
- ✅ Tests execute successfully
- ✅ Log output appears in the console
- ✅ Log format matches your configuration (JSON or plain)
- ✅ Log levels are respected (DEBUG logs only if level ≤ DEBUG)
Create a simple test to verify logging works:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.jupiter.api.Test;
public class LoggingTest {
private static final Logger log = LoggerFactory.getLogger(LoggingTest.class);
@Test
void testLogging() {
log.trace("This is a TRACE message");
log.debug("This is a DEBUG message");
log.info("This is an INFO message");
log.warn("This is a WARN message");
log.error("This is an ERROR message");
}
}Run this test:
mvn test -Dtest=LoggingTestYou should see all 5 log messages in the output (or up to the level configured in your properties file).
- Build completes without errors
- No SLF4J provider warnings
- Tests run and produce log output
- Log format matches configuration (JSON or plain)
- Log levels are being respected
- Package-specific log levels are working
If you're building a GraalVM native image, avaje-simple-logger is designed to work seamlessly.
avaje-simple-logger automatically initializes at GraalVM build time:
- The logger is set up during image build (not at runtime)
- Configuration from
avaje-logger.propertiesis processed at build time - Your native image will have logging ready to use
You should see this in the build output:
io.avaje.simplelogger.graalvm.BuildInitialization
This indicates avaje-simple-logger is being prepared for the native image.
native-image -cp target/my-app.jar com.mycompany.MyApplication my-appThe logger will be ready to use in the resulting native binary.
This section is optional. Use it if you need to change log levels at runtime (useful for K8s/Lambda).
- Production applications in Kubernetes
- Serverless functions (AWS Lambda)
- Applications using AWS AppConfig or similar configuration services
- Need to increase logging levels temporarily for troubleshooting
avaje-simple-logger integrates with avaje-config. When configuration changes start with log.level., they are automatically applied:
# Initial configuration
log.level.com.mycompany=WARN
# Later, if avaje-config updates this property:
log.level.com.mycompany=DEBUG
# The change is applied without restarting the applicationYou can also change log levels in your code:
import io.avaje.applog.LoggerContext;
import java.util.HashMap;
import java.util.Map;
// Change log levels programmatically
Map<String, String> levels = new HashMap<>();
levels.put("com.mycompany", "DEBUG");
levels.put("com.mycompany.payment", "TRACE");
LoggerContext.get().putAll(levels);Cause: avaje-simple-logger dependency is not in the classpath.
Solution:
- Verify the dependency is added to
pom.xml - Run
mvn dependency:treeto confirm it's present - Check the
<version>tag is correct - Run
mvn clean installto refresh your local repository
Cause: More than one SLF4J implementation is in the classpath (e.g., both Logback and avaje-simple-logger).
Solution:
- Run
mvn dependency:tree | grep -E "(logback|log4j|slf4j-simple|avaje-simple)"to find duplicates - Remove the old logging implementation (Logback, Log4j, etc.)
- Keep only avaje-simple-logger
Cause: Log level is set too high.
Solution:
- Lower
logger.defaultLogLevelin your properties file - Run the application again with
logger.defaultLogLevel=DEBUGorlogger.defaultLogLevel=TRACE - Check that your properties file is in the correct location (
src/main/resources/avaje-logger.properties)
Cause: logger.format=plain is set instead of json.
Solution:
- In
src/main/resources/avaje-logger.properties, setlogger.format=json - Rebuild:
mvn clean package - Re-run your application
Cause: File is in the wrong location or has wrong name.
Solution:
- Verify file location:
src/main/resources/avaje-logger.properties(exactly this path) - Verify filename:
avaje-logger.properties(exact case) - Check that
src/main/resourcesis marked as a resource directory in your IDE - Run
mvn clean packageto ensure resources are copied
Cause: Test properties file is missing or log level is too high.
Solution:
- Create
src/test/resources/avaje-logger-test.properties - Set
logger.defaultLogLevel=DEBUGorlogger.defaultLogLevel=INFO - Run tests with
mvn test
pom.xml (relevant sections):
<dependencies>
<!-- Remove logback-spring-boot-starter if present -->
<!-- Add avaje-simple-logger -->
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-simple-logger</artifactId>
<version>1.5-RC1</version>
</dependency>
<!-- Spring Boot dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>src/main/resources/application.properties:
spring.application.name=my-spring-app
server.port=8080src/main/resources/avaje-logger.properties:
logger.defaultLogLevel=info
logger.format=json
logger.component=my-spring-app
logger.environment=production
log.level.org.springframework=warn
log.level.org.springframework.web=info
log.level.com.mycompany=debugsrc/test/resources/avaje-logger-test.properties:
logger.format=plain
logger.defaultLogLevel=debug
log.level.org.springframework=warn
log.level.com.mycompany=debugParent pom.xml:
<project>
<groupId>com.mycompany</groupId>
<artifactId>my-project-parent</artifactId>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-simple-logger</artifactId>
<version>1.5-RC1</version>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>core</module>
<module>service</module>
<module>api</module>
</modules>
</project>Each module's pom.xml:
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-simple-logger</artifactId>
<!-- Version inherited from parent -->
</dependency>Shared properties file at root level or in a resources module:
src/main/resources/avaje-logger.properties(same in each module or in common resources)src/test/resources/avaje-logger-test.properties(same in each module or in common resources)
pom.xml:
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-simple-logger</artifactId>
<version>1.5-RC1</version>
</dependency>
<dependency>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</dependency>src/main/resources/avaje-logger.properties:
logger.defaultLogLevel=warn
logger.format=json
logger.component=my-native-app
logger.environment=production
log.level.com.mycompany=infoBuild command:
mvn clean package -PnativeRuntime:
./target/my-appThe logger is fully functional in the native binary, with all configuration from the properties file applied at build time.
You now have avaje-simple-logger configured in your Maven project!
- ✅ Added avaje-simple-logger dependency
- ✅ Configured production logging with
avaje-logger.properties - ✅ Configured test logging with
avaje-logger-test.properties - ✅ Optionally replaced an existing logging framework
- Customize configuration - Adjust log levels and format for your needs
- Start using logging - Begin logging in your code with SLF4J:
LoggerFactory.getLogger(MyClass.class) - Monitor logs - For JSON format, use a log aggregation service (ELK, Splunk, etc.)
- Enable dynamic configuration - If needed for K8s/Lambda, set up avaje-config
- Project README: https://github.com/avaje/avaje-simple-logger
- SLF4J Documentation: https://www.slf4j.org/
- avaje-config: https://avaje.io/config/
- GraalVM Native Image: https://www.graalvm.org/latest/reference-manual/native-image/