The Log4j2 API is richer than the SLF4J API, and many Log4j2 API features are not accessible via SLF4J. See below for details.

Features of the Log4j2 implementation, like Async Loggers, Lookups, Filters, Layouts and Appenders are controlled with configuration and are available regardless of the logging API you use in your application.

Please also see this answer to the different but related question of why it is safe to program to the Log4j2 API.

10 Log4j2 API features not available in SLF4J

(1) The Message API allows applications to log structured objects in addition to just text. Internally Log4j2 converts everything that is logged to a Message, and exposing this to the API opens up all kinds of possibilities for applications to interact with downstream logging components (filters, layouts, appenders). This can be useful if you're developing custom components as plugins to Log4j2, as well as when you're using the built-in ones. For a built-in example, see how StructuredDataMessage is used for fine-grained control over Rfc5424Layout.

(2) Java 8 lambda support allows you to lazily create parameters or log messages without explicitly checking if the requested log level is enabled.

// Java-8 style optimization: no need to explicitly check the log level: // the lambda expression is not evaluated if the TRACE level is not enabled logger.trace("Some long-running operation returned {}", () -> expensiveOperation());

(3) Mixing {}-style parameters with String::format %s %d -style parameters. The {} style has better performance and can be used with any parameter type, but the printf style gives fine grained control over the formatting. Log4j2 allows you to easily mix these parameter styles. For example:

logger.debug("Opening connection to {}...", someDataSource); logger.printf(Level.INFO, "Logging in user %1$s with birthday %2$tm %2$te,%2$tY", user.getName(), user.getBirthdayCalendar());

(4) CloseableThreadContext offers some extra convenience over the normal ThreadContext (MDC) in SLF4J: it automatically removes items when you're done. For example:

// Add to the ThreadContext map for this try block only; try (final CloseableThreadContext.Instance ctc = CloseableThreadContext .put("id", UUID.randomUUID().toString()) .put("loginId", session.getAttribute("loginId"))) { logger.debug("Message 1"); // call some other code that also does logging ... logger.debug("Message 2"); ... } // "id" and "loginId" are now removed from the ThreadContext map

(5) Log4j2's ThreadContext, in addition to key-value pairs, also has push and pop methods to support stack functionality (what used to be called NDC in Log4j 1).

(6) SLF4J does not support the FATAL log level.

(7) Log4j2 has support for custom log levels. These can be used with the log methods, for example: logger.log(Level.getLevel("FINE"), "... msg") , or you can generate a custom logger wrapper with convenience methods for your custom log levels.

(8) The Log4j2 API accepts any Object, not just Strings. This is one of the things that allow Log4j2 to be "garbage-free", meaning it will avoid allocating new Objects. Your Object is logged without creating any temporary Strings if it is a Number, a CharSequence or when it implements the (Log4j2) StringBuilderFormattable interface.

The Log4j2 API will also avoid creating vararg arrays if you log 10 parameters or less. SLF4J creates vararg arrays if you log more than 2 parameters.

(9) The above you get for free just by using the Log4j2 API directly. On top of that, if you really care about avoiding creating temporary objects (like some interactive games and low-latency financial applications do), you can avoid auto-boxing primitive parameters with the Unbox utility class.

(10) SLF4J Markers' use of coarse-grained synchronization may have performance impact for multi-threaded applications (SLF4J-240). See the Advanced Filtering section of this performance test results page.

Disclaimer: I contribute to Log4j2.