Introduction

The JBoss logging tools are used to create internationalized log statements and exceptions.

Getting Started

To get started you need to include three dependencies in your project.

JBoss Logging

Required at compile time and runtime.

JBoss Logging Annotations

Required at compile time only.

JBoss Logging Processor

Required at compile time only.

If you’re using maven here is an example pom.xml snippet:

<project>
    <dependencies>
        <!-- Required by the annotation processor and will be used at runtime -->
        <dependency>
            <groupId>org.jboss.logging</groupId>
            <artifactId>jboss-logging</artifactId>
        </dependency>

        <dependency>
            <groupId>org.jboss.logging</groupId>
            <artifactId>jboss-logging-annotations</artifactId>
            <version>3.0.2.Final</version>
            <!-- This is a compile-time dependency of this project, but is not needed at compile or runtime by other
                  projects that depend on this project.-->
            <scope>provided</scope>
            <optional>true</optional>
        </dependency>
    </dependencies>
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <!-- Define the annotation processor path. This is required on Java 22+. The other option is to
                             pass the -proc:full javac argument. -->
                        <annotationProcessorPaths>
                            <path>
                                <groupId>org.jboss.logging</groupId>
                                <artifactId>jboss-logging-processor</artifactId>
                                <version>3.0.2.Final</version>
                            </path>
                        </annotationProcessorPaths>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

Once your project is configured you can create either a message bundle interface or a message logger interface. For detailed information see the JavaDocs for the annotations.

Message Bundle Interfaces

Message bundle interfaces provide a way to internationalize exceptions or strings. A message bundle interface is annotated with @MessageBundle. Each method in must be annotated with @Message which will be used to determine the String used for either the return type or the message for the exception being returned.

The value for a @Message may contain an expression.

Expressions are in the form of ${key:defaultValue}. If the key is prefixed with sys. a system property is used. If the key is prefixed with env. an environment variable is used. In all other cases the properties are resolved from the org.jboss.logging.tools.expressionProperties path. If the key is not found in the properties the default value will be used.
Expressions are processed at compile time. The values will be hard-coded in the generated source files.

The following constraints are placed on methods in a message bundle:

  • The return type must be one of the follow

    • A java.lang.String

    • A java.lang.Throwable or a subtype of java.lang.Throwable

    • A java.lang.function.Supplier who’s return type fits one of the other two constraints above.

  • The method must be annotated with @Message or a message must be inheritable.

  • A method can have only one @Cause parameter.

  • A method can only have one @Producer parameter.

A message is inheritable if the two methods have the same name and same number of format parameters.
A format parameter is a parameter that has no annotations or is annotated with one of the following annotations; @FormatWith, @Pos or @Transform.

Example Message Bundle

@MessageBundle(projectCode = "CW") (1)
public interface ErrorMessages {
    ErrorMessages MESSAGES = Messages.getBundle(ErrorMessages.class);

    @Message("Version %d.%d.%d.%s") (2)
    String version(int major, int minor, int macro, String rel);

    @Message(id = 1, value = "Value '%s' is invalid")
    IllegalArgumentException invalidValue(Object value);

    @Message(id = 2, value = "Failure closing %s") (3)
    CloseException closeFailure(@Cause Throwable cause, @Param @Pos(1) Closeable c);

    CloseException closeFailure(@Cause Throwable cause, @Param @Pos(1) Closeable c, @Suppressed Throwable... errors);

    @Message(id = 3, value = "Parameter %s cannot be null")
    Supplier<String> nullParam(String name);

    @Message(id = 4, value = "Operation %s failed.")
    <T extends RuntimeException> T operationFailed(@Producer Function<String, T> fn, String name); (4)

    <T extends RuntimeException> T operationFailed(@Producer BiFunction<String, IOException, T> fn, @Cause IOException cause, String name);
}
1 The projectCode will be prepended to messages which have an id specified. For example with id = 1 the message will be prepended with CW000001. You can control the number padding with the length property on the annotation.
2 No id is specified for this message which means no id will be prepended on this message.
3 The @Param annotation tells the generator that the parameter should be used to construct the CloseException. The @Pos(1) annotation indicates the parameter should also be used when formatting the message.
4 The @Producer annotation indicates that the Function should be used to create the exception being returned.
Message bundle interfaces can also contain valid message logger methods.

Message Logger Interfaces

Logger interfaces provide a way to internationalize log messages. A logger interface is an interface annotated with @MessageLogger. Each method in must be annotated with @Message which will be used to determine the message to be logged.

The value for a @Message may contain an expression.

Expressions are in the form of ${key:defaultValue}. If the key is prefixed with sys. a system property is used. If the key is prefixed with env. an environment variable is used. In all other cases the properties are resolved from the org.jboss.logging.tools.expressionProperties path. If the key is not found in the properties the default value will be used.
Expressions are processed at compile time. The values will be hard-coded in the generated source files.

The following constraints are placed on methods in a message logger:

  • The method must have a void return type

  • The method must be annotated with @LogMessage

  • The method must be annotated with @Message or a message must be inheritable.

  • A method can have only one @Cause parameter.

A message is inheritable if the two methods have the same name and same number of format parameters.
A format parameter is a parameter that has no annotations or is annotated with one of the following annotations; @FormatWith, @Pos or @Transform.

A message logger interface may also extend the org.jboss.logging.BasicLogger. This allows the logging interface to be usable for standard logging. For example AppLogger.LOGGER.debugf("Initializing %s", this);

Example Message Logger

@MessageLogger(projectCode = "CW") (1)
public interface AppLogger extends BasicLogger {

    AppLogger LOGGER = Logger.getMessageLogger(AppLogger.class, AppLogger.class.getPackage().getName());

    @LogMessage
    @Once (2)
    @Message("%s version %d.%d.%d.%s") (3)
    void appVersion(CharSequence name, int major, int minor, int macro, String rel);

    @LogMessage(level = Logger.Level.ERROR) (4)
    @Message(id = 100, value = "Failure while closing %s")
    void closeFailure(@Cause Throwable cause, Object obj);

    @LogMessage(level = Logger.Level.WARN)
    @Message(id = 101, value = "Encoding %s could not be found. Defaulting to %s.")
    void encodingNotFound(String encoding, Charset dft);

    @LogMessage
    @Message(id = 102, value = "Cache size changed to '%d'")
    void cacheSizeChanged(@Transform(Transform.TransformType.SIZE) Collection<String> c);

    @LogMessage
    void cacheSizeChanged(@Transform(Transform.TransformType.SIZE) String... array);

    @LogMessage
    void cacheSizeChanged(@Transform(Transform.TransformType.SIZE) Map<String, Object> map);
}
1 The projectCode will be prepended to messages which have an id specified. For example with id = 100 the message will be prepended with CW000100. You can control the number padding with the length property on the annotation.
2 Ensures the log message is only written once.
3 No id is specified for this message which means no id will be prepended on this message.
4 Overrides the default level to ERROR to indicate an error message should be logged.

Reports

There are currently two types of reports that can be generated. The options are adoc or asciidoc for asciidoc and xml for XML.

The reports contain the following data:

  • The formatted message id.

  • A possible link to a resolution document for the error.

  • The unformatted message.

  • The log level, if applicable.

  • The return type, if applicable.

Annotations

Two annotations can be used to assist withing linking resolution documents for messages.

  • @BaseUrl

    • This annotation can be used on a type to define the base URL for linking resolution documents. This annotation is not required for links to be created on reports.

  • @ResolutionDoc

    • This annotation is used to define information about the resolution document for creating links. If placed on an interface all methods that have a defined id will have a link generated. This can also be placed individually on the method to only generate links for specific id’s.

Example Use Cases

Below are some example use case snippets from the examples.

/**
 * Writes the value of the object to the file.
 *
 * @param value the value to write, cannot be {@code null}
 *
 * @throws UncheckedIOException if an error occurs writing the data
 */
public void write(final Object value) {
    AppLogger.LOGGER.appVersion("ContentWriter", 1, 0, 0, "Beta1"); (1)
    Objects.requireNonNull(value, ErrorMessages.MESSAGES.nullParam("value")); (2)
    write(Objects.requireNonNull(value, ErrorMessages.MESSAGES.nullParam("value")).toString());
}

/**
 * Writes the value to the file.
 *
 * @param value the value to write, cannot be {@code null} or an {@linkplain String#isEmpty() empty string}.
 *
 * @throws UncheckedIOException if an error occurs writing the data
 */
public void write(final String value) {
    AppLogger.LOGGER.appVersion("ContentWriter", 1, 0, 0, "Beta1");
    if (Objects.requireNonNull(value, ErrorMessages.MESSAGES.nullParam("value")).isEmpty()) {
        throw ErrorMessages.MESSAGES.invalidValue(value); (3)
    }
    try {
        synchronized (outputLock) {
            writer.write(value);
            writer.newLine();
            if (autoFlush) {
                flush();
            }
        }
    } catch (IOException e) {
        throw ErrorMessages.MESSAGES.operationFailed(UncheckedIOException::new, e, "write"); (4)
    }
}

@Override
public void close() {
    try {
        synchronized (outputLock) {
            writer.close();
        }
        AppLogger.LOGGER.tracef("ContentWriter %s was successfully closed.", this);
    } catch (Exception e) {
        throw ErrorMessages.MESSAGES.closeFailure(e, this);
    }
}

/**
 * Safely close this writer logging any errors that occur during closing.
 */
public void safeClose() {
    try {
        synchronized (outputLock) {
            writer.close();
        }
        AppLogger.LOGGER.tracef("ContentWriter %s was successfully closed.", this);
    } catch (Exception e) {
        AppLogger.LOGGER.closeFailure(e, this); (5)
    }
}
1 Logs the application version. Note this uses the @Once annotation to indicate this should only be logged once regardless of which write method is used.
2 The ErrorMessages.nullParam() returns a java.lang.function.Supplier. This allows the message to be lazily formatted only if the value is null.
3 Throws a message if the value is an empty string.
4 Uses a java.lang.function.BiFunction to create a new UncheckedIOException with the caught exception set as the cause.
5 Logs the caught exception instead of throwing a new exception.

Translation Property Files

The translation properties files must exist in the same directory structure as the interface. The name of the properties file InterfaceName.i18n_language_country_variant.properties. For example if you have a class named org.jboss.logging.tools.examples.ErrorMessages and you want to translate this into French you create a properties file called ErrorMessages.i18n_fr.properties in a directory org/jboss/logging/tools/examples.

Annotation Processor Options

You can pass options to an annotation processor with the -A compiler option. For example to disable generating the @Generated annotation from being placed on the generated source files you would pass -Aorg.jboss.logging.tools.addGeneratedAnnotation=false to the compiler command.

General

Option Description

debug

This option turns on debug logging for the processor

org.jboss.logging.tools.expressionProperties

This option allows you to define a path where, at compile-time, expressions in messages can be resolved.

org.jboss.logging.tools.addGeneratedAnnotation

If set to false the @Generated annotation will not be placed on the generated source files. The default is true.

In Java 9 the @javax.annotation.Generated was moved to @javax.annotation.processor.Generated. The processor attempts to determine which annotation to use by attempting to find the @javax.annotation.Generated first. If it fails the @javax.annotation.processor.Generated is attempted. If neither can be found no annotation will be placed on the generated implementations.
Expressions are in the form of ${key:defaultValue}. If the key is prefixed with sys. a system property is used. If the key is prefixed with env. an environment variable is used. In all other cases the properties are resolved from the org.jboss.logging.tools.expressionProperties path. If the key is not found in the properties the default value will be used.
Expressions are processed at compile time. The values will be hard-coded in the generated source files.

Translation Options

Option Description

translationsFilesPath

The base path for the translated properties files. This defaults to the location where new class files are placed.

skipTranslations

If set to true source files with the translated tet will not be generated. The default is false.

generatedTranslationFilesPath

If defined this indicates the path a skeleton file should be generated for the interface. The generated skeleton file will be placed in a directory that matches the package with a name that matches the interface with a .i18n_locale_COUNTRY_VARIANT.properties suffix.

org.jboss.logging.tools.level

Sets the maximum level to include in the generated skeleton files. For example if set to INFO the skeleton files will not contain any properties where the log level was set to DEBUG or TRACE.

org.jboss.logging.tools.generated.skip.index

By default when generating a skeleton translation file an index will be appended to the format pattern. For example Example %s and %d becomes Example %1$s and %2$d. This option allows this behavior to be disabled.

Report Options

Option Description

org.jboss.logging.tools.report.type

Indicates the type of report that should be generated. The current report types are adoc for asciidoc or xml for XML.

org.jboss.logging.tools.report.path

The path where the generated reports should be placed. This defaults to the location where new class files are placed.

org.jboss.logging.tools.report.title

An optional title for the report. For asciidoc this defaults to Messages. For XML the <title> element is left off.