diff --git a/gcp-firebase/.gitignore b/gcp-firebase/.gitignore
new file mode 100644
index 0000000000..d0c04ea3fe
--- /dev/null
+++ b/gcp-firebase/.gitignore
@@ -0,0 +1 @@
+/firebase-service-account.json
diff --git a/gcp-firebase/pom.xml b/gcp-firebase/pom.xml
new file mode 100644
index 0000000000..c563099ad6
--- /dev/null
+++ b/gcp-firebase/pom.xml
@@ -0,0 +1,48 @@
+
+ 4.0.0
+
+ com.baeldung
+ parent-boot-2
+ 0.0.1-SNAPSHOT
+ ../parent-boot-2
+
+ gcp-firebase
+
+
+ 9.1.1
+
+
+
+
+ com.google.firebase
+ firebase-admin
+ ${firebase-admin.version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
\ No newline at end of file
diff --git a/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/FirebasePublisherApplication.java b/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/FirebasePublisherApplication.java
new file mode 100644
index 0000000000..904ae88f00
--- /dev/null
+++ b/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/FirebasePublisherApplication.java
@@ -0,0 +1,12 @@
+package com.baeldung.gcp.firebase.publisher;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class FirebasePublisherApplication {
+
+ public static void main(String[] args ) {
+ SpringApplication.run(FirebasePublisherApplication.class,args);
+ }
+}
diff --git a/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/config/FirebaseConfiguration.java b/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/config/FirebaseConfiguration.java
new file mode 100644
index 0000000000..bbfb63d089
--- /dev/null
+++ b/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/config/FirebaseConfiguration.java
@@ -0,0 +1,57 @@
+package com.baeldung.gcp.firebase.publisher.config;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.google.api.client.http.HttpTransport;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.firebase.FirebaseApp;
+import com.google.firebase.FirebaseOptions;
+import com.google.firebase.messaging.FirebaseMessaging;
+
+@Configuration
+@EnableConfigurationProperties(FirebaseProperties.class)
+public class FirebaseConfiguration {
+
+ private final FirebaseProperties firebaseProperties;
+
+ public FirebaseConfiguration(FirebaseProperties firebaseProperties) {
+ this.firebaseProperties = firebaseProperties;
+ }
+
+ @Bean
+ GoogleCredentials googleCredentials() {
+ try {
+ if (firebaseProperties.getServiceAccount() != null) {
+ try( InputStream is = firebaseProperties.getServiceAccount().getInputStream()) {
+ return GoogleCredentials.fromStream(is);
+ }
+ }
+ else {
+ // Use standard credentials chain. Useful when running inside GKE
+ return GoogleCredentials.getApplicationDefault();
+ }
+ }
+ catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ @Bean
+ FirebaseApp firebaseApp(GoogleCredentials credentials) {
+ FirebaseOptions options = FirebaseOptions.builder()
+ .setCredentials(credentials)
+ .build();
+
+ return FirebaseApp.initializeApp(options);
+ }
+
+ @Bean
+ FirebaseMessaging firebaseMessaging(FirebaseApp firebaseApp) {
+ return FirebaseMessaging.getInstance(firebaseApp);
+ }
+}
diff --git a/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/config/FirebaseProperties.java b/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/config/FirebaseProperties.java
new file mode 100644
index 0000000000..44be70cc5d
--- /dev/null
+++ b/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/config/FirebaseProperties.java
@@ -0,0 +1,25 @@
+package com.baeldung.gcp.firebase.publisher.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.core.io.Resource;
+
+@ConfigurationProperties(prefix = "gcp.firebase")
+public class FirebaseProperties {
+ private Resource serviceAccount;
+
+
+ /**
+ * @return the serviceAccount
+ */
+ public Resource getServiceAccount() {
+ return serviceAccount;
+ }
+
+ /**
+ * @param serviceAccount the serviceAccount to set
+ */
+ public void setServiceAccount(Resource serviceAccount) {
+ this.serviceAccount = serviceAccount;
+ }
+
+}
diff --git a/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/controller/ConditionMessageRepresentation.java b/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/controller/ConditionMessageRepresentation.java
new file mode 100644
index 0000000000..f0d470a44c
--- /dev/null
+++ b/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/controller/ConditionMessageRepresentation.java
@@ -0,0 +1,35 @@
+package com.baeldung.gcp.firebase.publisher.controller;
+
+public class ConditionMessageRepresentation {
+
+ private String condition;
+ private String data;
+
+ /**
+ * @return the condition
+ */
+ public String getCondition() {
+ return condition;
+ }
+
+ /**
+ * @param condition the condition to set
+ */
+ public void setCondition(String condition) {
+ this.condition = condition;
+ }
+
+ /**
+ * @return the data
+ */
+ public String getData() {
+ return data;
+ }
+
+ /**
+ * @param data the data to set
+ */
+ public void setData(String data) {
+ this.data = data;
+ }
+}
diff --git a/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/controller/FirebasePublisherController.java b/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/controller/FirebasePublisherController.java
new file mode 100644
index 0000000000..ca7467531e
--- /dev/null
+++ b/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/controller/FirebasePublisherController.java
@@ -0,0 +1,109 @@
+package com.baeldung.gcp.firebase.publisher.controller;
+
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.google.firebase.messaging.AndroidConfig;
+import com.google.firebase.messaging.AndroidFcmOptions;
+import com.google.firebase.messaging.ApnsConfig;
+import com.google.firebase.messaging.BatchResponse;
+import com.google.firebase.messaging.FcmOptions;
+import com.google.firebase.messaging.FirebaseMessaging;
+import com.google.firebase.messaging.FirebaseMessagingException;
+import com.google.firebase.messaging.Message;
+import com.google.firebase.messaging.MulticastMessage;
+import com.google.firebase.messaging.Notification;
+
+@RestController
+public class FirebasePublisherController {
+
+ private final FirebaseMessaging fcm;
+
+ public FirebasePublisherController(FirebaseMessaging fcm) {
+ this.fcm = fcm;
+ }
+
+ @PostMapping("/topics/{topic}")
+ public ResponseEntity postToTopic(@RequestBody String message, @PathVariable("topic") String topic) throws FirebaseMessagingException {
+
+ Message msg = Message.builder()
+ .setTopic(topic)
+ .putData("body", message)
+ .build();
+
+ String id = fcm.send(msg);
+ return ResponseEntity
+ .status(HttpStatus.ACCEPTED)
+ .body(id);
+ }
+
+ @PostMapping("/condition")
+ public ResponseEntity postToCondition(@RequestBody ConditionMessageRepresentation message ) throws FirebaseMessagingException {
+
+ Message msg = Message.builder()
+ .setCondition(message.getCondition())
+ .putData("body", message.getData())
+ .build();
+
+ String id = fcm.send(msg);
+ return ResponseEntity
+ .status(HttpStatus.ACCEPTED)
+ .body(id);
+ }
+
+
+ @PostMapping("/clients/{registrationToken}")
+ public ResponseEntity postToClient(@RequestBody String message, @PathVariable("registrationToken") String registrationToken) throws FirebaseMessagingException {
+
+ Message msg = Message.builder()
+ .setToken(registrationToken)
+ .putData("body", message)
+ .build();
+
+ String id = fcm.send(msg);
+ return ResponseEntity
+ .status(HttpStatus.ACCEPTED)
+ .body(id);
+ }
+
+ @PostMapping("/clients")
+ public ResponseEntity> postToClients(@RequestBody MulticastMessageRepresentation message) throws FirebaseMessagingException {
+
+ MulticastMessage msg = MulticastMessage.builder()
+ .addAllTokens(message.getRegistrationTokens())
+ .putData("body", message.getData())
+ .build();
+
+ BatchResponse response = fcm.sendMulticast(msg);
+
+ List ids = response.getResponses()
+ .stream()
+ .map(r->r.getMessageId())
+ .collect(Collectors.toList());
+
+ return ResponseEntity
+ .status(HttpStatus.ACCEPTED)
+ .body(ids);
+ }
+
+ @PostMapping("/subscriptions/{topic}")
+ public ResponseEntity createSubscription(@PathVariable("topic") String topic,@RequestBody List registrationTokens) throws FirebaseMessagingException {
+ fcm.subscribeToTopic(registrationTokens, topic);
+ return ResponseEntity.ok().build();
+ }
+
+ @DeleteMapping("/subscriptions/{topic}/{registrationToken}")
+ public ResponseEntity deleteSubscription(@PathVariable String topic, @PathVariable String registrationToken) throws FirebaseMessagingException {
+ fcm.subscribeToTopic(List.of(registrationToken), topic);
+ return ResponseEntity.ok().build();
+ }
+}
diff --git a/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/controller/MulticastMessageRepresentation.java b/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/controller/MulticastMessageRepresentation.java
new file mode 100644
index 0000000000..5d86e160e0
--- /dev/null
+++ b/gcp-firebase/src/main/java/com/baeldung/gcp/firebase/publisher/controller/MulticastMessageRepresentation.java
@@ -0,0 +1,33 @@
+package com.baeldung.gcp.firebase.publisher.controller;
+
+import java.util.List;
+
+public class MulticastMessageRepresentation {
+
+ private String data;
+ private List registrationTokens;
+ /**
+ * @return the message
+ */
+ public String getData() {
+ return data;
+ }
+ /**
+ * @param message the message to set
+ */
+ public void setData(String data) {
+ this.data = data;
+ }
+ /**
+ * @return the registrationTokens
+ */
+ public List getRegistrationTokens() {
+ return registrationTokens;
+ }
+ /**
+ * @param registrationTokens the registrationTokens to set
+ */
+ public void setRegistrationTokens(List registrationTokens) {
+ this.registrationTokens = registrationTokens;
+ }
+}
diff --git a/gcp-firebase/src/main/resources/application.properties b/gcp-firebase/src/main/resources/application.properties
new file mode 100644
index 0000000000..aacbde0d92
--- /dev/null
+++ b/gcp-firebase/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+# Service account location. Can be a filesystem path or a classpath resource
+gcp.firebase.service-account=file:firebase-service-account.json
\ No newline at end of file
diff --git a/gcp-firebase/src/test/java/com/baeldung/gcp/firebase/publisher/controller/FirebasePublisherControllerLiveTest.java b/gcp-firebase/src/test/java/com/baeldung/gcp/firebase/publisher/controller/FirebasePublisherControllerLiveTest.java
new file mode 100644
index 0000000000..eae4fc0e57
--- /dev/null
+++ b/gcp-firebase/src/test/java/com/baeldung/gcp/firebase/publisher/controller/FirebasePublisherControllerLiveTest.java
@@ -0,0 +1,48 @@
+package com.baeldung.gcp.firebase.publisher.controller;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.net.URI;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+class FirebasePublisherControllerLiveTest {
+
+ @LocalServerPort
+ int serverPort;
+
+ @Autowired
+ TestRestTemplate restTemplate;
+
+ @Test
+ void testWhenPostTopicMessage_thenSucess() throws Exception{
+
+ URI uri = new URI("http://localhost:" + serverPort + "/topics/my-topic");
+ ResponseEntity response = restTemplate.postForEntity(uri, "Hello, world", String.class);
+
+ assertNotNull(response);
+ assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
+ assertNotNull(response.getBody());
+ }
+
+ @Test
+ void testWhenPostClientMessage_thenSucess() throws Exception{
+
+ URI uri = new URI("http://localhost:" + serverPort + "/clients/fake-registration1");
+ ResponseEntity response = restTemplate.postForEntity(uri, "Hello, world", String.class);
+
+ assertNotNull(response);
+ assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
+ assertNotNull(response.getBody());
+ }
+
+}
diff --git a/messaging-modules/rabbitmq/README.md b/messaging-modules/rabbitmq/README.md
index d91d268b2b..6a74c297fc 100644
--- a/messaging-modules/rabbitmq/README.md
+++ b/messaging-modules/rabbitmq/README.md
@@ -8,3 +8,4 @@ This module contains articles about RabbitMQ.
- [Pub-Sub vs. Message Queues](https://www.baeldung.com/pub-sub-vs-message-queues)
- [Channels and Connections in RabbitMQ](https://www.baeldung.com/java-rabbitmq-channels-connections)
+