Using Log4J2 with Spring Boot

Spring boot starter projects enable quick development boot applications.  Starter projects has a default dependency on spring-boot-starter-logging. This library configures logback as the default logging implementation. However some may prefer to use log4J2 instead of Logback to write application logs. Both works with the SLF4J facade API and hence it is possible to switch from Logback to log4j2 without changing any code.

Modify the gradle file of your spring boot project to use log4j2 instead of logback implementation,

  • Exclude the spring-boot-starter-logging dependency.
  • Add an explicit dependency on spring-boot-starter-log4j2.

Sample Gradle File to Configure Log4J2 (build.gradle)

buildscript {
    ext {
        springBootVersion = '1.5.2.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter-web') {
       exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'   
    }
    
    compile('org.springframework.boot:spring-boot-starter-log4j2')
    
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

Sample POM File to Configure Log4J2 (pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.quickprogrammingtips.springboot</groupId>
    <artifactId>logdemo2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>logdemo2</name>
    <description>Spring Boot Demo</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
        <relativePath />
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Sample Spring Boot Controller Class (SLF4J Logging)

package com.quickprogrammingtips.springboot;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloWorldController {
    private static final Logger LOG = LoggerFactory.getLogger(HelloWorldController.class);

    @RequestMapping("/hello")
    @ResponseBody
    String home() {
        LOG.warn("sending hello world response...");
        return "Hello World!";
    }
}

The logging configuration in application.properties works even when you replace logback with log4j2. However if you want to fine tune log4j2 logging, you can add log4j2.xml or log4j2-spring.xml to the src/main/resources folder of your spring boot project.

Why Replace Logback with Log4J2?

Logback and log4j2 are good logging implementations designed to replace the older log4J implementation. However log4j2 has some advantages over logback,

  • Log4j2 is designed as an audit logging framework. During reconfiguration, events are retained and exceptions from appenders can be visible to the application.
  • Uses high speed asynchronous implementation. In multithreaded applications up to 10x performance compared to logback.
  • Simple and easy to use plugin system and support for message objects.
  • Advanced support for filters, custom log levels, layouts and Java 5 concurrency support.

Dependencies of Spring-boot-starter-logging

  • logback-classic – An SLF4J native logging implementation
  • jcl-over-slf4j – Apache Commons Logging implemented over SLF4J
  • jul-to-slf4j – JDK logging to SLF4J bridge
  • log4j-over-slf4j – Log4J implemented over SLF4J

Dependencies of Spring-boot-starter-log4j2

  • log4j-slf4j-impl – SLF4J logging binding of log4j2 core
  • log4j-api – Log4j2 API
  • log4j-core – Log4j2 implementation
  • jcl-over-slf4j – Apache Commons Logging implemented over SLF4J
  • jul-to-slf4j – JDK logging to SLF4J bridge

JDK Logging (JUL) in Spring Boot Applications

When writing spring boot applications, it is recommended to use SLF4J API for logging. However it is also possible to use the logging implementation provided by JDK directly. However JDK logging implementation (JUL) provides a different set of logging levels compared to SLF4J or Log4J. The logging levels used by JDK library are,

FINEST > FINER > FINE > CONFIG > INFO > WARNING > SEVERE

SLF4J and other logging implementations such as Log4j use the following log levels,

ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF

Spring boot’s SLF4J bridge maps the log levels to the JDK logging levels using the following table.

FINEST  => TRACE
FINER   => DEBUG
FINE    => DEBUG
CONFIG  => INFO
INFO    => INFO
WARNING => WARN
SEVERE  => ERROR

Spring boot propagates the logging configuration set in application.properties to JDK logging classes as well. Hence even if you are using both logging libraries in your application (JDK logging and Logback with SLF4J), the same set of configuration in application.properties can be used! For example, the following configuration sets the root logger to WARN (WARNING) level and all application classes to TRACE (FINEST). Note that you need to use the log levels defined by SLF4J.

# application.properties values
logging.level.root=WARN
logging.level.com.quickprogrammingtips=TRACE

To configure only the logback system, create a custom logback.xml or logback-spring.xml. To configure only the JDK logging classes (JUL), create a custom logging.properties file. These files should be created in src/main/resources folder.

Using JDK Logging (JUL) in Spring Boot Classes

Here is a simple spring boot controller which uses the JDK logging library,

package com.quickprogrammingtips.springboot;

import java.util.logging.Logger;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloWorld2Controller {
    private static final Logger LOG = Logger.getLogger(HelloWorld2Controller.class.getName());

    @RequestMapping("/hello2")
    @ResponseBody
    String home() {
        LOG.finest("Sending hello world message!");
        return "Hello World!";
    }
}

How to Enable Color in Spring Boot Console Log

Spring boot supports color coding of logging output displayed on the console. This enables us to quickly identify any errors or warnings logged by spring boot system. On most systems color coding works automatically. However if you are using a Mac terminal, you will find that no color coding is applied to the spring boot console log output. Mac terminal does have support for color, but somehow spring boot is unable to detect it.

spring-boot-console-log-without-color

The solution is simple. Just add the following property in the application.properties located in src/main/resources folder of your spring boot project. The default value for the following attribute is "detect".

spring.output.ansi.enabled=always

Run the application again. Voila! Now you have the color coding enabled on your spring boot application console log.

spring-boot-console-log-with-color

How to Create a Fat Jar Using Maven

Maven is a popular build tool for Java projects. Using Maven it is possible specify all the third party dependencies of a project. During Java compilation, Maven adds these dependency library jars  automatically to the classpath. Maven can also be used to package a project as a distributable jar library. However when you package a project as a jar using maven, the dependent jars are not packaged. This means that in order to run the jar on a different machine, you need to provide all the dependent jars on the classpath.

The advantage of the above approach is that your distributable jars are small. However the downside is that the application won’t run without providing the dependencies explicitly on the classpath. This is a problem with many cloud java runtime providers such as Amazon Web Services (AWS lambda) which expect the jar to be self contained with all dependencies.

Luckily Maven provides a plugin named Apache Maven Shade Plugin for creating a distributable jar with all the dependencies. Such a jar is know as a fat jar (also called uber jar) since it is substantially larger than the original. Maven Shade Plugin works by modifying the maven package task.

How to Create a Fat Jar Using Maven

To enable fat jar creation, just add the following xml snippet to your maven pom file. This will ensure that whenever you call the maven package task, all the dependent library code is also added to the generated fat jar.

<build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.0.0</version>
        <configuration>
        </configuration>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
</build>

Following are the major features of Apache Maven Shade Plugin,

  • It is possible to relocate the dependent classes to avoid conflicts with the environment where the jar is running. Shade plugin rewrites the entire byte code under the relocated package. This ensures that a completely private copy of the dependency jar classes are added to the project!
  • It is possible to filter classes or resources from the dependency jars. This gives fine grained control over how the fat jar is created.
  • Apache maven shade plugin also contains a number of resource transformers for merging resources from several dependency jars. For example, ApacheLicenseResourceTransformer can be used to prevent duplicate license files.
  • Apache maven shade plugin also has support for creating executable jar files.

Example: Generating Fat/Uber Jar Using Shade Plugin

Following is  a simple project which uses Apache commons lang library for string comparison. Following is our main class which uses the third party class StringUtils.

package com.quickprogrammingtips.demo.shadedemo;

import java.util.Scanner;

import org.apache.commons.lang3.StringUtils;

public class App {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Please enter your name: ");
        String name = scanner.nextLine();

        if (StringUtils.isBlank(name)) {
            System.out.println("Sorry, cannot proceed without a name!");
        }else {
            System.out.println("Hello "+name);
        }

        scanner.close();
    }
}

Following is our original pom file for the project. Notice the dependency of commons-lang3 library.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.quickprogrammingtips.demo</groupId>
    <artifactId>shadedemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>shadedemo</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.5</version>
        </dependency>
    </dependencies>
</project>

To create an ordinary jar, run the following command on the terminal. The jar file is created in the target subfolder.

mvn package

Now try to run our program from the terminal from the console (after switching to target folder),

java -cp shadedemo-0.0.1-SNAPSHOT.jar com.quickprogrammingtips.demo.shadedemo.App

Here is the output of the program,

Please enter your name: QPT

Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/lang3/StringUtils

    at com.quickprogrammingtips.demo.shadedemo.App.main(App.java:13)

Caused by: java.lang.ClassNotFoundException: org.apache.commons.lang3.StringUtils

    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)

    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)

    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)

    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)

    … 1 more

Our program throws a runtime exception since it not able to load the commons-lang3 classes. Let us now modify our pom.xnl file to use the apache maven shade plugin,

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.quickprogrammingtips.demo</groupId>
    <artifactId>shadedemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>shadedemo</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.5</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Run the following command on the terminal to create the distributable fat jar,

mvn package

Now the run program again with the following command. Now the application works without any runtime errors!

java -cp shadedemo-0.0.1-SNAPSHOT.jar com.quickprogrammingtips.demo.shadedemo.App

Please enter your name: QPT

Hello QPT

Apache maven shade plugin also supports executable jar files. Let us modify the pom.xml again to configure an executable jar file,

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.quickprogrammingtips.demo</groupId>
    <artifactId>shadedemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>shadedemo</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.5</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <transformers>
                        <transformer
                            implementation= "org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                            <mainClass>com.quickprogrammingtips.demo.shadedemo.App</mainClass>
                        </transformer>
                    </transformers>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Now you can directly run the jar file without specifying a class name,

java -jar shadedemo-0.0.1-SNAPSHOT.jar

To verify the inclusion of the dependent class files in the fat jar, you can extract the jar and inspect its contents.

contents-of-apache-shaded-jar

What is New in Spring Boot 2.0

Spring Boot 2 is a major upcoming release (scheduled for November 2017) with a number of dependency upgrades. Spring boot requires a more recent version of the following tools and frameworks.

  • Spring Boot 2 requires spring framework 5 or above. Spring framework 5 now requires Java 8 and Java EE 7 API. It also has full compatibility with JDK 9. Spring 5 also requires Tiles 3 and Hibernate 5 (older versions are no longer supported). Spring 5 also introduces a number of features for reactive programming model.
  • Spring Boot 2 requires JDK 8 or later. It won’t work on JDK 6 and 7!
  • Spring Boot 2 requires Jetty 9.4 or above, Tomcat 8.5 or above, Hibernate 5.2 or above and Gradle 3.4 or above.

Following are some of the core changes in spring boot platform,

  • The spring security and spring session filters are now auto-configured with ASYNC, ERROR, and REQUEST dispatcher types. This is to align spring boot defaults with spring 5 defaults.
  • The @ConditionalOnBean now uses logical AND for combining conditions (this was OR in spring boot 1.x). This is an important change for spring boot 1.x to 2.x migration.
  • Spring boot 2.x removed support for the CRaSH remote shell since the CRaSH project itself is no longer actively supported.

If you are interested in the upcoming spring boot releases and spring boot roadmap, check out the official milestones page.