Simple Publisher Subscriber in Java I am new to Java and had some difficulty creating this simple Publisher Subscriber class. It finally seems to be working and I wanted to check if I have been following best practices and see if there are any improvements I can make.

The following Java class accepts any topics as a string (e.g. "rand_int") and forwards any messages to that topic to any subscribers.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

public class PubSub {
  private Map&lt;String, ArrayList&lt;Consumer&lt;?&gt;&gt;&gt; topicsSubscribers =
      new HashMap&lt;String, ArrayList&lt;Consumer&lt;?&gt;&gt;&gt;();
  private static PubSub pubSubSingleton = null;

  public static PubSub getInstance() {
    if (pubSubSingleton == null)
      pubSubSingleton = new PubSub();

    return pubSubSingleton;
  }

  public &lt;T&gt; void publish(String topic, T message) {
    ArrayList&lt;Consumer&lt;?&gt;&gt; subscribers = this.topicsSubscribers.get(topic);
    if (subscribers == null)
      return;

    for (Consumer subscriberConsumer : subscribers) {
      subscriberConsumer.accept(message);
    }
  }

  public synchronized &lt;T&gt; void subscribe(String topicName, Consumer&lt;T&gt; subscriberCallback) {
    ArrayList&lt;Consumer&lt;?&gt;&gt; subscribers = this.topicsSubscribers.get(topicName);
    if (subscribers == null) {
      subscribers = new ArrayList&lt;Consumer&lt;?&gt;&gt;();
      subscribers.add(subscriberCallback);
      this.topicsSubscribers.put(topicName, subscribers);
    } else {
      subscribers.add(subscriberCallback);
    }
  }
}


Example usage:

public class SomeClass {
  public SomeClass() {
    PubSub.getInstance().subscribe("rand_int", this::randIntCallback);
    PubSub.getInstance().publish("rand_int", 46);
  }

  public randIntCallback(int number) {
    System.out.println(number);
  }
}


The above code should output "46".

As I said, I am very new to Java and would like some feedback on my code to help improve. So any help on this is very appreciated.
Initializing object variables


  private Map&lt;String, ArrayList&lt;Consumer&lt;?&gt;&gt;&gt; topicsSubscribers =
      new HashMap&lt;String, ArrayList&lt;Consumer&lt;?&gt;&gt;&gt;();



The standard is to set the types as the interfaces, like Map and List.  

    private final Map&lt;String, List&lt;Consumer&lt;?&gt;&gt;&gt; topicSubscribers = new HashMap&lt;&gt;();


Also, you don't need to specify the contents of &lt;&gt; in the implementation instantiation.  The compiler will work that out for you.  

Initializing class variables


  private static PubSub pubSubSingleton = null;

  public static PubSub getInstance() {
    if (pubSubSingleton == null)
      pubSubSingleton = new PubSub();

    return pubSubSingleton;
  }



You don't have to do it this way.  You can simply say 

    private static final PubSub pubSubSingleton = new PubSub();

    public static PubSub getInstance() {
        return pubSubSingleton;
    }

    private PubSub() {
    }


This saves the null check each time while still only initializing once.  

However, Singleton is generally considered an anti-pattern now.  You may want to consider alternatives.  One possibility would be a separate instance for each message type.  

Your original code does not make the constructor private.  

The Java standard is four column indents rather than the two you are using.  

Formatting


    if (subscribers == null)
      return;



If you are going to use the statement form, you should probably keep it one line:  

    if (subscribers == null) return;


If it's too complicated to fit into one line, you should probably use the block form instead.  

I actually use the block form consistently regardless.  

    if (subscribers == null) {
        return;
    }


Factor out the common


    if (subscribers == null) {
      subscribers = new ArrayList&lt;Consumer&lt;?&gt;&gt;();
      subscribers.add(subscriberCallback);
      this.topicsSubscribers.put(topicName, subscribers);
    } else {
      subscribers.add(subscriberCallback);
    }



This could be 

    if (subscribers == null) {
        subscribers = new ArrayList&lt;Consumer&lt;?&gt;&gt;();
        topicsSubscribers.put(topicName, subscribers);
    }

    subscribers.add(subscriberCallback);


You don't have to do the add before the put.  It's the same object either way.  Otherwise, you'd have to do the put after the add in the non-null case.