Skip to content
Switch branches/tags

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

GitHub Workflow Status Known Vulnerabilities Sonar Coverage Maven Central Java 11+

Java Util Logging Improver (JULI)

Although rarely the preferred framework java.util.logging (JUL) is embedded into Java so aids minimal applications but is not the easiest to use. This library is here to make life a little easier:

  • Minimal layer to improve:
    • Type-safety to reduce errors,
    • Improve maintenance overhead by reducing complexity,
    • Simplify unit testing (especially with CDI),
    • Reduce improve performance by deferring array initialisation and auto-boxing until actually needed,
    • Automatic unwrapping of java.util.function.*Supplier and java.util.Optional parameters.
  • Asynchronous handlers to mitigate logging impact on critical path,
  • Fallback handlers to mitigate loss of logs to aid debugging.

Log Wrapper

The wrapper uses Annotation Processing to generate an implementation that includes many logging best practices without a developer needing to worry about these.

ℹ️ JBoss Logging Tools was the inspiration but designed to be even simpler, leaner and not dependent on JBoss Log Manager.

Lets get started... first import dependencies:

  <scope>provided</scope> <!-- only needed during compilation -->

Define a logger interface:


public interface MyLog {
  @Message("Hello {0}")  // <- defaults to `Info` level
  void hello(String name);

  @Message(value = "Oh no! {0}", level = Level.ERROR)  // <- 'Error' level
  void error(String name, Throwable t);  // <- Throwables must be last parameter

  @Message("Hello {0}")
  void unwrap(Supplier<String> name);  // <- value extracted on the calling thread if #isLoggable passes

  @Message("Number {0}")
  void number(int value);  // <- primitives only auto-boxed if the log level is consumed. w00t!

  @Message("Another umber {0}")
  void numberUnwrap(IntSuppler value);  // <- primitive suppliers work too!

This will generate a class$impl which actually does the logging. For Maven this can be seen in the target/generated-sources/annotations/ folder for reference.

To get an instance use uk.dansiviter.juli.LogProducer:

public class MyClass {
  private final static MyLog LOG = LogProducer.log(MyLog.class);

  public void myMethod() {

ℹ️ The log levels are reduced to just ERROR, WARN, INFO, DEBUG and TRACE as, frankly, that's all you really need.


This can perform automatic injection of dependencies via CDI:

public class MyClass {
  private MyLog log;

  public void myMethod() {

This will inject a @Dependent scoped instance of the logger to prevent proxying.

ℹ️ Testing can be done easily by injecting a mock of MyLog which helps validating behaviour which is especially pertinent for warning and error messages.

Asynchronous Handlers

JUL handlers are all synchronous which puts IO directly within the path of execution; this is a bottleneck. To address this, this library has a very simple uk.dansiviter.juli.AsyncHandler implementation that uses java.util.concurrent.Flow to asynchronously process log events. Using this can dramatically improve the performance of 'chatty' logging at the expense of a little memory and CPU. There is only one concrete implementation as the moment which is uk.dansiviter.juli.AsyncConsoleHandler which can be used as a direct replacement for java.util.logging.ConsoleHandler.

⚠️ If the buffer saturates, then the much of the performance benefits of the asynchronous handler can be lost. However, once the pressure is reduced this will return, due to this it should still outperform a synchronous implementation.

ℹ️ com.lmax:disruptor has been trialed as an alternative to using Java 9 Flow but its bulk (~90KB) and no significant out-of-the-box performance improvement is has been discounted (see #6).

Fallback Handler

Some handlers have external factors that may prevent a log record being flushed. This might include a remote log aggregator where a network failure prevents the message being delivered. To prevent loss of log records the uk.dansiviter.juli.FallbackHandler can be used to manage this case. For example:


With the above configuration, if either MyRemoteHandler delegate cannot be created or Handler#publish(...) fails the fallback handler will be used instead. If no fallback is defined then AsyncConsoleHandler is used.


Java Util Logging Improver (JULI) to improve both implementation, performance and testing of logging with `java.util.logging`.