4 min read

Elegant way to send firebase push notifications from Spring Boot.

September 10, 2020

Mobile notifications play a major role in user engagement. With the firebase’s new SDK for java, sending FCM push notification from a java Spring Boot application got a lot easier.

Usually, FCM notifications were done via a rest call to https://fcm.googleapis.com/fcm/send with a JSON request. Even though this method seems simple to connect to Firebase Cloud Messaging(FCM), once you span across iOS and web-push, things can get out of hand pretty soon. For this reason, Firebase SDK comes with predefined methods that can help clear this mess.

Maven dependencies for firebase messaging

To include the Firebase-SDK you need to add the following maven dependency to your project.

        <dependency>
            <groupId>com.google.firebase</groupId>
            <artifactId>firebase-admin</artifactId>
            <version>7.0.0</version>
        </dependency>

Service Account Private Key Generation from FCM

The next thing you have to do is to generate a service-account.json. For this open the Service Accounts in firebase console and click Generate Private Key.

Generate private Key

This will download a file in the form of <firebase-project>-firebase-adminsdk-<hash>.json. Save this file src/java/resources as firebase-service-account.json.

Java configuration for messaging

In our spring boot application, Let’s create an object for FirebaseMessaging and register it as a bean. This bean will be used to send notifications.

@Bean
FirebaseMessaging firebaseMessaging() throws IOException {
    GoogleCredentials googleCredentials = GoogleCredentials
            .fromStream(new ClassPathResource("firebase-service-account.json").getInputStream());
    FirebaseOptions firebaseOptions = FirebaseOptions
            .builder()
            .setCredentials(googleCredentials)
            .build();
    FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions, "my-app");
    return FirebaseMessaging.getInstance(app);
}

Here new ClassPathResource("firebase-service-account.json").getInputStream() is an easier way to create an InputStream for a file that is in classpath. With this we created a FirebaseApp object from which we got our FirebaseMessaging instance.

I wrote a simple FirebaseMessagingService by using the bean that we just created. In a very basic use case, the class would look like this.

@Service
public class FirebaseMessagingService {

    private final FirebaseMessaging firebaseMessaging;

    public FirebaseMessagingService(FirebaseMessaging firebaseMessaging) {
        this.firebaseMessaging = firebaseMessaging;
    }


    public String sendNotification(Note note, String token) throws FirebaseMessagingException {

        Notification notification = Notification
                .builder()
                .setTitle(note.getSubject())
                .setBody(note.getContent())
                .build();

        Message message = Message
                .builder()
                .setToken(token)
                .setNotification(notification)
                .putAllData(note.getData())
                .build();

        return firebaseMessaging.send(message);
    }

}

How it works?

Here, The Notification object captures what Title and Body to show on the mobile notification.

The Messageobject has multiple builder methods to deal with various scenarios.

  • You can send a message to a specific recipient by using setToken(String) or you can send it to anyone who is subscribed to a topic by using setTopic(String)
  • You can pass additional data to the mobile applications using putData(String, String) or putAllData(Map<String, String>).
  • You can customize options specific to Android, iOS and web clients using the below builder methods respectively.

    • setAndroidConfig()
    • setApnsConfig()
    • setWebpushConfig()
  • Each of these methods is backed by a Builder specific to their type.

Once you have created a message object with appropriate values, then you can call firebaseMessaging.send(Message) to submit the request. The method will return a String which represents the message ID that was sent during the API call. There is no need to store this value. Note that there can be a FirebaseMessagingException if there is an error while processing the message while sending. So make sure you handle it.

Let’s write a simple test API call.

@Data
public class Note {
    private String subject;
    private String content;
    private Map<String, String> data;
    private String image;
}

/*******************************/

@RequestMapping("/send-notification")
@ResponseBody
public String sendNotification(@RequestBody Note note,
                               @RequestParam String token) throws FirebaseMessagingException {
    return firebaseService.sendNotification(note, token);
}

Verifying push notification

A simple curl command with a sample post date yields the following result.

$ curl -X POST "http://localhost:8080/send-notification?topic=gold" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "some subject",
    "content": "Some long content",
    "image": "https://somedomain.com/example.jpg",
    "data": {
      "key1": "Value 1",
      "key2": "Value 2",
      "key3": "Value 3",
      "key4": "Value 4"
    }
  }'

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 53
Date: Thu, 10 Sep 2020 14:21:50 GMT
Keep-Alive: timeout=60
Connection: keep-alive

projects/spring-demo/messages/2282285117414704507

Notice how the response is projects/spring-demo/messages/2282285117414704507 which is the message ID of the fired message to FCM. This message can be used to debug the mobile client code.

Note that in the GitHub example, You will have to replace the firebase-service-account.json downloaded from your FCM project.