Send push notifications from Spring Boot server-side application using FCM

As you probably noticed, I familiarize myself with the subject of Firebase Cloud Messaging push notifications recently.

This time I would like to share with you my push notifications server-side application. The app is made with the Spring Boot framework.

The provided example covers most common notifications sending use cases such as: sending push notification to a topic, directly to the users’ device or sending messages with additional data payload.

This post is not a step-by-step tutorial. I’ll focus on the most important parts. A fully working example is available on GitHub.

If you don’t have a client-side application to receive push notifications yet, you should consider the use of the Ionic app I described earlier. Have fun!

Let’s go

Let’s start from the Firebase integration.

First of all, you have to generate your own Firebase SDK admin key. Basically, it’s a JSON file with your Firebase project credentials. You’ll need it for server-side authorization (more information).

Login into your Firebase console. Go to Project settings -> Service accounts and then click Generate new private key button.

 

Generate and save the file. We’ll use it in the next steps.

Spring Boot application

Put generated Firebase Admin SDK JSON file inside your Spring Boot project files (if you don’t have one you can generate it here).

In my case, I created a google folder inside src/main/resources. Then, in my application.properties, I added a new key/value pair containing a file path.

app.firebase-configuration-file=google/push-notifications-example-firebase-adminsdk.json

 

Now we’ll need Firebase dependencies here so let’s add some. I was using Maven as a dependency manager. In my pom.xml I added:

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

Now we have to initialize our Firebase application. This is the time to use our app.firebase-configuration-file. I used @Value annotation to inject the path value to the String field.

...

@Service
public class FCMInitializer {

    @Value("${app.firebase-configuration-file}")
    private String firebaseConfigPath;

    Logger logger = LoggerFactory.getLogger(FCMInitializer.class);

    @PostConstruct
    public void initialize() {
        try {
            FirebaseOptions options = new FirebaseOptions.Builder()
                    .setCredentials(GoogleCredentials.fromStream(new ClassPathResource(firebaseConfigPath).getInputStream())).build();
            if (FirebaseApp.getApps().isEmpty()) {
                FirebaseApp.initializeApp(options);
                logger.info("Firebase application has been initialized");
            }
        } catch (IOException e) {
            logger.error(e.getMessage());
        }
    }

}
FCMInitializer.java

The initialize() method is being called on application start-up thanks to @PostConstruct annotation.

Sending push notifications

The next step is to prepare our push notifications service class.
But first of all, I separated following use cases:

  • send push notification to the topic using sample data (defined in application.properties)
  • send scheduled sample push notification to the topic (every minute)
  • send a push notification with additional data payload (user-defined key/value data object, look at the docs for more information)
  • send push notification without an additional data payload
  • send push notification to a specific user

Sending push notification without data payload example from FCMService class:

@Service
public class FCMService {

    ...

    public void sendMessageWithoutData(PushNotificationRequest request)
            throws InterruptedException, ExecutionException {
        Message message = getPreconfiguredMessageWithoutData(request);
        String response = sendAndGetResponse(message);
        logger.info("Sent message without data. Topic: " + request.getTopic() + ", " + response);
    }

    ...

    private String sendAndGetResponse(Message message) throws InterruptedException, ExecutionException {
        return FirebaseMessaging.getInstance().sendAsync(message).get();
    }

    private AndroidConfig getAndroidConfig(String topic) {
        return AndroidConfig.builder()
                .setTtl(Duration.ofMinutes(2).toMillis()).setCollapseKey(topic)
                .setPriority(AndroidConfig.Priority.HIGH)
                .setNotification(AndroidNotification.builder().setSound(NotificationParameter.SOUND.getValue())
                        .setColor(NotificationParameter.COLOR.getValue()).setTag(topic).build()).build();
    }

    private ApnsConfig getApnsConfig(String topic) {
        return ApnsConfig.builder()
                .setAps(Aps.builder().setCategory(topic).setThreadId(topic).build()).build();
    }

    private Message getPreconfiguredMessageWithoutData(PushNotificationRequest request) {
        return getPreconfiguredMessageBuilder(request).setTopic(request.getTopic())
                .build();
    }

    ...

    private Message.Builder getPreconfiguredMessageBuilder(PushNotificationRequest request) {
        AndroidConfig androidConfig = getAndroidConfig(request.getTopic());
        ApnsConfig apnsConfig = getApnsConfig(request.getTopic());
        return Message.builder()
                .setApnsConfig(apnsConfig).setAndroidConfig(androidConfig).setNotification(
                        new Notification(request.getTitle(), request.getMessage()));
    }


}

If you really want to you can test it right now by calling proper methods. For clarity purposes, I added another layer – PushNotificationService which will be used directly by PushNotificationController in the further steps.

Let’s take a look at PushNotificationService:

...

@Service
public class PushNotificationService {

    @Value("#{${app.notifications.defaults}}")
    private Map<String, String> defaults;

    private Logger logger = LoggerFactory.getLogger(PushNotificationService.class);
    private FCMService fcmService;

    public PushNotificationService(FCMService fcmService) {
        this.fcmService = fcmService;
    }

    @Scheduled(initialDelay = 60000, fixedDelay = 60000)
    public void sendSamplePushNotification() {
        try {
            fcmService.sendMessageWithoutData(getSamplePushNotificationRequest());
        } catch (InterruptedException | ExecutionException e) {
            logger.error(e.getMessage());
        }
    }

    public void sendPushNotification(PushNotificationRequest request) {
        try {
            fcmService.sendMessage(getSamplePayloadData(), request);
        } catch (InterruptedException | ExecutionException e) {
            logger.error(e.getMessage());
        }
    }

    public void sendPushNotificationWithoutData(PushNotificationRequest request) {
        try {
            fcmService.sendMessageWithoutData(request);
        } catch (InterruptedException | ExecutionException e) {
            logger.error(e.getMessage());
        }
    }


    public void sendPushNotificationToToken(PushNotificationRequest request) {
        try {
            fcmService.sendMessageToToken(request);
        } catch (InterruptedException | ExecutionException e) {
            logger.error(e.getMessage());
        }
    }


    private Map<String, String> getSamplePayloadData() {
        Map<String, String> pushData = new HashMap<>();
        pushData.put("messageId", defaults.get("payloadMessageId"));
        pushData.put("text", defaults.get("payloadData") + " " + LocalDateTime.now());
        return pushData;
    }


    private PushNotificationRequest getSamplePushNotificationRequest() {
        PushNotificationRequest request = new PushNotificationRequest(defaults.get("title"),
                defaults.get("message"),
                defaults.get("topic"));
        return request;
    }


}
PushNotificationService

As you see we have a @Value annotation again over here.
Why? Because we have a bunch of default values to pass to our methods.

So instead of injecting single value from properties to particular String variable, I decided to use Map type for simplicity. It still uses the same @Value annotation.

Again, we store the defaults in application.properties. So the app.notifications.defaults key looks like this:

app.notifications.defaults={topic: 'common', title: 'Common topic - Hello', message: 'Sending test message \uD83D\uDE42', token: 'ss22t03wz208eg:APA2idkkow223FE_0v5yHxqCLTyxAQafj6nWaqi4QzwZTW004q1PUux63UsFN', payloadMessageId: '123', payloadData: 'Hello. This is payload content.'}
application.properties

Moreover, you can see we’re using the FCMService directly, passing notifications requests objects to it.

For scheduling, I used @Scheduled annotation with an initial delay of 1min. (initialDelay parameter) and 1min. sending interval (fixedDelay parameter). Remember to annotate your Application class with @EnableScheduling.

Endpoints

Let’s interact with our services and build a controller.

For presentation purposes I exposed my service methods as REST API endpoints in order to send custom, direct messages (to the topic or specific subscriber) with/without data payload or trigger the default notification sending.

...

@RestController
public class PushNotificationController {

    private PushNotificationService pushNotificationService;

    public PushNotificationController(PushNotificationService pushNotificationService) {
        this.pushNotificationService = pushNotificationService;
    }

    @PostMapping("/notification/topic")
    public ResponseEntity sendNotification(@RequestBody PushNotificationRequest request) {
        pushNotificationService.sendPushNotificationWithoutData(request);
        return new ResponseEntity<>(new PushNotificationResponse(HttpStatus.OK.value(), "Notification has been sent."), HttpStatus.OK);
    }

...

    @GetMapping("/notification")
    public ResponseEntity sendSampleNotification() {
        pushNotificationService.sendSamplePushNotification();
        return new ResponseEntity<>(new PushNotificationResponse(HttpStatus.OK.value(), "Notification has been sent."), HttpStatus.OK);
    }
}
PushNotificationController.java fragment

Test REST endpoints with cURL

Let’s test our endpoints. I used cURL for this task but you can use your favorite HTTP requests tools or write a small client app for this.

  • GET /notification – Trigger sample notification with default values sending
curl -H "Content-Type: application/json" -X GET http://localhost:8080/notification
  • POST /notification/topic – Send a message to a specific topic
curl -d '{"title":"Hello", "message":"The message...", "topic":"contactTopic"}' -H "Content-Type: application/json" -X POST http://localhost:8080/notification/topic
  • POST /notification/token – Send a message to a specific device (with the token)
curl -d '{"title":"Hey you!", "message":"Watch out!", "token":"cct00ebz8eg:APA91bFcTkFE_0Qafj6nWv5yHxqCLTyxAaqi4QzwsFNLP5M9G78X8Z5UMZTW004q1PUux63Ut-1WMGVToMNTdB3ZfO8lCZlc4lGpxm7LBdWfkhaUxdbpQ5xIO5cAb-w9H2dBLNHT7i-U", "topic": ""}' -H "Content-Type: application/json" -X POST http://localhost:8080/notification/token
  • POST /notification/data – Send a message to a specific topic with additional payload data.
    Please note: in this case I took defaults from application.properties as sample payload. In your application, you should probably use third-party API data or persistent data from your database.
curl -d '{"title":"Hello", "message":"Data message", "topic":"contactTopic"}' -H "Content-Type: application/json" -X POST http://localhost:8080/notification/data

 

If succeeded you should receive following JSON response with code 200:

{

    "status": 200,
    "message": "Notification has been sent."

}

 

Results

Well… If configured properly it just works. I used my Ionic app and Android phone as a client.

Take a look at this screenshot with received notifications:

In this case, the client-side app doesn’t handle additional payload data.

Are you curious about the yellow color of notifications? I set it in NotificationParameter enum along with sound option value (which is default in my case).
See the reference for more additional fields description.

Summary

In this post, I covered basic Firebase Cloud Messaging integration with Spring Boot application. If you’re looking for server-side push notification implementation I think this is the way to go.

As I mentioned before, the fully working example is available on GitHub. If you’re looking for a basic client-side mobile application please take a look at my previous Ionic push notifications app post.

Please note that the provided example was tested with Android application. Although I provided some basic Apple Push Notification Service (APNS) configuration you have to deal with potential issues when developing an iOS application. If you managed to do it please let me know.

Good luck!

48 thoughts on “Send push notifications from Spring Boot server-side application using FCM”

  1. Thanks for this tutorials, I have followed your steps closely, but i am having an error, this is the error message “FirebaseApp with name [DEFAULT] doesn’t exist. “, i will be glad if you can find time to help me out. Thanks

        1. Hi Brian. You have to include JSON file generated in your Firebase application console.

          But I tested it once again and it’s not necessary to have this particular file in the back-end project. It works just fine without it. It’s important on the client side – for example in the Ionic app.

  2. Thanks for the tutorial, I have followed the steps and placed the firebase-adminsdk json file inside the google folder. I want to send a notification to the specific device. I have Token, Sender ID, Device ID, and FCMToken, but not getting how to use them in the application properties file.

    URL use: http://localhost:8080/notification/token

    app.notifications.defaults={title:’Hey you!’, message:’Watch out!’, token:’dZ2rMLNNOwM:PA91bF3LbFKiXqOVQR8NcvTyT5M4vsxPZLvRPEDevAoEI0m4sNLxA4eyKqJ5g6zJIzbmushrjbmgHxzJsWnkjrhDGiH-gza-yyncN3wnLV2DRhw3gMGxFT_J7FHrl-5wHW7qKWo’, topic: ‘Any’}

    1. Hi Anand. Thanks for your comment.

      First of all, I’m not sure if it’s because of comments formatting but it looks like your quotation marks are invalid.

      You’re using where the valid ones are: ' (single quotation) or " (double quotation mark).

      Please take a look at this fragment where I used my properties:
      GitHub

      In this example I used it to load the defaults into single Map object using @Value annotation.
      You can try to load them into separate String objects as well. Or even, for the test purposes, you can use your FCM values directly.

      Good luck

  3. Hello Rafal,

    I have deployed the spring boot server side app and it runs okay. But the challenges i am having now is the UI. How do I consume the notification. Kindly provide a source code showing how the api is consume and the messages are displayed

    1. Hi Oma. In my case the client side was built with Ionic Framework. Please take a look here for a reference.

      There’s also a link to the GitHub repository. Hope it helps.

  4. Hi,
    Your tutorial is totally good. I have it works too in my localhost. But, when the project was deployed in weblogic server (i’m packaging it to .war), the notification sending action doesn’t work.

    Can you help me what’s wrong with it?
    Thanks.

  5. Hi. By including firebase dependency, google signin verification shows an error :

    java.lang.nosuchmethoderror: com.google.api.client.util.base64.decodebase64(ljava/lang/string;)[b when fcm present

    This error is coming at following line:
    GoogleIdToken googleToken = verifier.verify(requestHeader);

    Can you help me to solve this issue?

  6. Hi @rafal this FCM notifiction tutorial is very helpful to me.please keep posting more about spring technology

    thanks for the post
    Nirav Patel

    1. Hi Nirav, thanks for your comment, I really appreciate it.

      On the blog I’m going to focus on Spring-related technologies in the nearest future, so keep up to date.

      Which aspects of Spring interest you the most?

  7. Hello Rafal,
    Thank you for such a great article. It work very well.
    I am planning to deploy the server app on Heroku.
    My concern is, is it okay to deploy the project with the Firebase Admin SDK Json file with Private Key in resources folder onto Heroku server?

    Thank you in advance for your help

    1. Hi Abdul, thanks for your comment.

      You should put it into the `resources` folder inside your Spring Boot project. Which means in your source code, before building the project. As I presume, on the Heroku you’ll deploy an application that has been already built – which is *.war or *.jar file.

      So don’t worry, as long as you don’t share the file with your private key publically, it’s all okay.
      Firebase integration library needs this file to handle push notification sending.

      Good luck with your deployment

      1. Thank you very much for the reply. On Heroku, I submit my repo and it is build on the Heroku server, which means, I have to push my source files there. I dont know if I can deply *.war or *.jar onto Heroku.

        Thank you again for your reply

        1. Okay Abdul, I understand now. So Heroku does the deploy for you.
          I assume it’s a git repo. So as long as it stays non-public it should be safe and accessible only for you and your project’s collaborators.

          Good luck

  8. Hello Rafal,
    The tutorial is perfect but the thing is after following the steps, when trying to set a specific token and calling the /notification/token page or using the command it does not work giving the following error : Whitelabel Error Page

    This application has no explicit mapping for /error, so you are seeing this as a fallback.
    Sun Mar 08 16:28:55 EET 2020
    There was an unexpected error (type=Method Not Allowed, status=405).
    Request method ‘GET’ not supported
    org.springframework.web.HttpRequestMethodNotSupportedException: Request method ‘GET’ not supported

    1. Hey George.

      Thanks for your comment.
      It looks like something is wrong with your controller class. Make sure if your `RequestMapping`s are done correctly.

      Take care, Rafal

  9. One more thing, can you please guide me how can I send “click_action” with the FCM notification. I want to start activity according to the sent data

    Thank you in advance

    1. Hey Abdul, I have never tried it but it looks like you just have to put another key-value pair into push notification data map.

      Like this:

      pushData.put(“click_action”, “action_to_be_performed”);

    1. Hi Marios.

      What do you mean by proxy? It sounds more like a network layer configuration related issue?

      Thanks for your comment

      Rafal

  10. Thank you your post !

    I have a one issues. When I call api push message. Firebase return response “c.t.f.s.FirebaseCloudMessageService : SenderId mismatch”. Can you help me ? . Thank you very much.

    1. Hi Tuan

      Thanks for your comment.

      Please check if you have your Firebase Admin SDK JSON file in place or try to regenerate it.

      Hope it helps

  11. Hey I used this tutorial and has been working great for me… until last week. Does this still work? Now when trying to run this I get this error:

    com.google.api.client.http.HttpResponseException: 401 Unauthorized
    Unexpected HTTP response with status: 401; body: null

    1. Hey Jacob

      Please try to recheck your Firebase Admin SDK JSON files.

      It definitely looks like a Google API connection error.

      Take care!

  12. Hi, Rafal.

    Excellent example! I have a doubt. How can I send specials characters in push notification body? I’m triying to send notifications in spanish. For example: áéíóú

  13. Can you explain how you have connected the client side application on the phone for this particular application in firebase? It is very confusing.

    1. Hi Nivedita!
      Thanks for your comment.

      In this text I described the back-end side only.
      That’s correct.

      The mobile application I used for receiving push notifications is described here. I built it using Ionic Framework.

      Take care

      1. Okay. Thank You for your help!

        Further, I was wondering if you could tell me if it is possible to use this to connect a web application that we are making using React JS instead of the Ionic Framework App.

        1. Sure thing.

          But please note that you have to integrate your client with a particular Firebase application. Not with the back-end app, I described in this post.
          Here we just send a request to the FCM and let the Firebase do all the magic with the message delivery.

          I have no experience with React but you try to can search for the terms like ‘FCM + React’.
          For example: https://github.com/pavelpashkovsky/react-fcm

  14. Hi Rafal,
    I want to read resources/google/service-acount-file.json file from database using pojo.
    FirebaseOptions options = new FirebaseOptions.Builder()
    .setCredentials(GoogleCredentials.fromStream(new ClassPathResource(firebaseConfigPath).getInputStream())).build();
    At the place of firebaseConfigPath, I want to use string JSON.
    How can I use it?

Leave a Reply

Your email address will not be published. Required fields are marked *