1

I have a class ValueRepository class and want to use a logger in it. The following code always throws a NullPointerException.

public enum ValueRepository {

INSTANCE;

private static final Logger LOGGER = LoggerFactory.getLogger(ValueRepository.class.getName());

private Map<String, Set<String>> key2value;
private Map value2key;

ValueRepository() {

    key2value = new HashMap();
    value2key = new HashMap();

    load("keyValue.txt");
}

public int size() {
    return key2value.size();
}

public boolean hasKey(String key) {
    return key2value.containsKey(key);
}

public boolean hasValue(String value) {
    return value2key.containsKey(value);
}

public Set<String> getValues(String key) {
    return key2value.get(key);
}

public String getKey(String value) {
    return (String) value2key.get(value);
}

private void load(String filePath) {

    BufferedReader br;
    try {
        br = IOUtils.fileReaderAsResource(filePath);
        String line = null;
        while ((line = br.readLine()) != null) {
            LOGGER.info("test");
            line = line.trim();
        }
        br.close();
    } catch (IOException io) {
        LOGGER.error("Can't load the file: " + filePath);
        io.printStackTrace();
    }
}

public static void main(String[] args) {

        // do nothing
    }

Shouldn't this work?

This question is very different from the link. It's specifically about why it doesn't work in logger in enum.

EDIT:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/congmi/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.6.2/log4j-slf4j-impl-2.6.2.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/congmi/.m2/repository/org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.apache.logging.slf4j.Log4jLoggerFactory]
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.NullPointerException
    at com.qe.repository.ValueRepository.load(ValueRepository.java:56)
    at com.qe.repository.ValueRepository.<init>(ValueRepository.java:26)
    at com.qe.repository.ValueRepository.<clinit>(ValueRepository.java:14)

Process finished with exit code 1
user697911
  • 10,043
  • 25
  • 95
  • 169

2 Answers2

2

Because you designed your ValueRepository singleton as an enum you got this initialization sequence of static constants:

  1. INSTANCE (implicitly calling the constructor)
  2. LOGGER

You need to change this initialization sequence to:

  1. LOGGER
  2. INSTANCE

You can't reach this sequence with enum, because the Java syntax insists that the enum instances are the very first.

That means, you need to implement your singleton as a normal class, not as an enum.

public class ValueRepository {

    private static final Logger LOGGER = LoggerFactory.getLogger(ValueRepository.class.getName());

    public static final ValueRepository INSTANCE = new ValueRepository();

    // remaining code unchanged

}
Thomas Fritsch
  • 9,639
  • 33
  • 37
  • 49
0

Short Answer: In your enum the static LOGGER variable is null until the constructor finishes. There are a few options for you.

  1. Make the logger an instance variable (remove static),
  2. Don't reference LOGGER during construction (throw an exception instead).
  3. Load the data into a static field in a static initializer block, rather during the constructor.

Long Answer: In general, enum static fields will not resolve until the instances are built, whether or not you've got a singleton enum, or multiple enum entries.

That means any time you reference a private static final field that requires another piece of code running (aka - not a string or primitive) during the instance construction, it will fail with a NullPointerException.

See this example in action here

import java.util.Date;

public class HelloWorld{

 public static void main(String []args){
    System.out.println("Hello World");
   final WierdEnum instance = WierdEnum.INSTANCE;
   instance.printStuff();
 }

 static enum WierdEnum {
     INSTANCE;

        private static final String test = "hello";
        private static final Date date = new Date();


     WierdEnum() {
       printStuff();
     }

     void printStuff() {
       System.out.println("Static string: " + test);
       System.out.println("Static date: " + date);
     }
 }
}

Resolves to:

$java -Xmx128M -Xms16M HelloWorld
Hello World
Static string: hello
Static date: null
Static string: hello
Static date: Mon Apr 16 18:57:27 UTC 2018

The problem is that the class loading order is causing you to ending up with null class variables during the instance construction. This is a different order for an enum than for a class

I suspect you'd like to keep this an enum since you're using it as a singleton pattern and you probably have this pattern elsewhere in your code. You have a few options:

1. Change the logger to an instance rather than class variable by removing static. We don't normally use an instance variable for a logger because it would have a logger for each instance of the object in the system. In this case, there's only one! So it's no problem.

private final Logger LOGGER = LoggerFactory.getLogger(ValueRepository.class);

2. OR Don't use the logger during construction. Instead of logging, throw a new exception if there is a problem reading the file. I suspect that's preferable, since it doesn't look like you can recover from a failed file read.

} catch (IOException io) {
    throw new RuntimeException("Can't load the file: " + filePath, io);
}

3. Load the data into static fields in a static initializer block.

private static Map<String, Set<String>> key2value = new HashMap<>();
private static Map value2key = new HashMap();

static {
    load("keyValue.txt");
}

private static void load(String filePath) {
...
Josh Hull
  • 1,723
  • 1
  • 16
  • 24