From 1053e863c8b4f0e8abc1e58a445afa8bebca6190 Mon Sep 17 00:00:00 2001 From: "Edward L." Date: Wed, 29 Apr 2026 21:48:23 +0300 Subject: [PATCH 1/8] refactor(config): rename .imports Signed-off-by: Edward L. --- ...figure.AutoConfiguration.imports => AutoConfiguration.imports} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename firebase-spring-boot/src/main/resources/META-INF/spring/{org.springframework.boot.autoconfigure.AutoConfiguration.imports => AutoConfiguration.imports} (100%) diff --git a/firebase-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/firebase-spring-boot/src/main/resources/META-INF/spring/AutoConfiguration.imports similarity index 100% rename from firebase-spring-boot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports rename to firebase-spring-boot/src/main/resources/META-INF/spring/AutoConfiguration.imports From 0f8cbbae6637829a964e4fa4ad91a3fb550f16e2 Mon Sep 17 00:00:00 2001 From: "Edward L." Date: Sat, 16 May 2026 21:04:01 +0300 Subject: [PATCH 2/8] fix(config): fail on NPE Signed-off-by: Edward L. --- .../firebase/DefaultFirebaseOptionsFactory.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseOptionsFactory.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseOptionsFactory.java index 246e6fe..7569b65 100644 --- a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseOptionsFactory.java +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseOptionsFactory.java @@ -9,9 +9,15 @@ public FirebaseOptions create(FirebaseProperties properties) { var mapper = PropertyMapper.get().alwaysApplyingWhenNonNull(); var ops = FirebaseOptions.builder(); mapper.from(properties.getDatabaseAuthVariableOverride()).to(ops::setDatabaseAuthVariableOverride); - mapper.from(properties.getDatabaseUrl()).to(ops::setDatabaseUrl); + mapper.from(properties.getDb()) + .as(FirebaseDatabaseProperties::getUrl) + .whenHasText() + .to(ops::setDatabaseUrl); mapper.from(properties.getProjectId()).to(ops::setProjectId); - mapper.from(properties.getStorageBucket()).to(ops::setStorageBucket); + mapper.from(properties.getStorage()) + .as(StorageProperties::getBucket) + .whenHasText() + .to(ops::setStorageBucket); mapper.from(properties.getServiceAccountId()).to(ops::setServiceAccountId); mapper.from(credsFactory.create(properties)).to(ops::setCredentials); mapper.from(properties.getConnectTimeout()).to(ops::setConnectTimeout); From 000fcbecfce9424ebe8db84b43eb9bb28158b6ff Mon Sep 17 00:00:00 2001 From: "Edward L." Date: Sat, 16 May 2026 21:05:08 +0300 Subject: [PATCH 3/8] refactor(config): remove unused `io.github.justedlev.firebase.config.FirebaseConfigurationProperties#setDefaultApp` method Signed-off-by: Edward L. --- .../firebase/config/FirebaseConfigurationProperties.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/config/FirebaseConfigurationProperties.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/config/FirebaseConfigurationProperties.java index c13f6c0..a5903b9 100644 --- a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/config/FirebaseConfigurationProperties.java +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/config/FirebaseConfigurationProperties.java @@ -30,9 +30,4 @@ public class FirebaseConfigurationProperties { public FirebaseProperties getDefaultApp() { return apps.get(DEFAULT_APP_NAME); } - - public FirebaseConfigurationProperties setDefaultApp(FirebaseProperties properties) { - apps.put(DEFAULT_APP_NAME, properties); - return this; - } } From 41bdef45b86da0d6b07f00e8e90f27afe579109f Mon Sep 17 00:00:00 2001 From: "Edward L." Date: Sat, 16 May 2026 21:06:38 +0300 Subject: [PATCH 4/8] feat(config): add beans init - add `defaultStorageClient` bean init - add `defaultFirestore` bean init Signed-off-by: Edward L. --- .../firebase/FirebaseDatabaseProperties.java | 1 + .../firebase/FirebaseProperties.java | 8 +++--- .../firebase/FirestoreProperties.java | 14 ++++++++++ .../justedlev/firebase/StorageProperties.java | 15 +++++++++++ .../FirebaseAutoConfiguration.java | 20 +++++++++++++- .../FirebaseAutoConfigurationTest.java | 26 ++++++------------- 6 files changed, 60 insertions(+), 24 deletions(-) create mode 100644 firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirestoreProperties.java create mode 100644 firebase-spring-boot/src/main/java/io/github/justedlev/firebase/StorageProperties.java diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseDatabaseProperties.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseDatabaseProperties.java index 29ba9fd..679e4a9 100644 --- a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseDatabaseProperties.java +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseDatabaseProperties.java @@ -11,4 +11,5 @@ @Builder public class FirebaseDatabaseProperties { private boolean enabled; + private String url; } diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseProperties.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseProperties.java index ccce450..272eb60 100644 --- a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseProperties.java +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseProperties.java @@ -5,7 +5,6 @@ import lombok.Data; import lombok.NoArgsConstructor; -import java.util.HashMap; import java.util.Map; import static com.google.firebase.FirebaseApp.DEFAULT_APP_NAME; @@ -21,11 +20,8 @@ public class FirebaseProperties { @lombok.Builder.Default private String name = DEFAULT_APP_NAME; - @lombok.Builder.Default - private Map databaseAuthVariableOverride = new HashMap<>(); - private String databaseUrl; + private Map databaseAuthVariableOverride; private String projectId; - private String storageBucket; private String serviceAccountId; private String credentials; private Integer connectTimeout; @@ -34,4 +30,6 @@ public class FirebaseProperties { private FirebaseAuthProperties auth = new FirebaseAuthProperties(); private FirebaseDatabaseProperties db = new FirebaseDatabaseProperties(); private FirebaseMessagingProperties messaging = new FirebaseMessagingProperties(); + private StorageProperties storage = new StorageProperties(); + private FirestoreProperties firestore = new FirestoreProperties(); } diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirestoreProperties.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirestoreProperties.java new file mode 100644 index 0000000..512f864 --- /dev/null +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirestoreProperties.java @@ -0,0 +1,14 @@ +package io.github.justedlev.firebase; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FirestoreProperties { + private boolean enabled; +} diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/StorageProperties.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/StorageProperties.java new file mode 100644 index 0000000..3ef42ea --- /dev/null +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/StorageProperties.java @@ -0,0 +1,15 @@ +package io.github.justedlev.firebase; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class StorageProperties { + private boolean enabled; + private String bucket; +} diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAutoConfiguration.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAutoConfiguration.java index 7aa851c..5b87fd0 100644 --- a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAutoConfiguration.java +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAutoConfiguration.java @@ -1,7 +1,10 @@ package io.github.justedlev.firebase.autoconfigure; +import com.google.cloud.firestore.Firestore; import com.google.firebase.FirebaseApp; import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.cloud.FirestoreClient; +import com.google.firebase.cloud.StorageClient; import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.messaging.FirebaseMessaging; import io.github.justedlev.firebase.DefaultFirebaseCredentialsFactory; @@ -57,7 +60,7 @@ public FirebaseApp defaultFirebaseApp(FirebaseOptionsFactory optionsFactory, Fir @Bean @ConditionalOnBean(name = "defaultFirebaseApp") @ConditionalOnBooleanProperty(prefix = PREFIX + ".apps.default.db", value = "enabled", matchIfMissing = true) - @ConditionalOnProperty(prefix = PREFIX, value = "apps.default.database-url") + @ConditionalOnProperty(prefix = PREFIX, value = "apps.default.db.url") public FirebaseDatabase defaultFirebaseDatabase(FirebaseApp firebaseApp) { return FirebaseDatabase.getInstance(firebaseApp); } @@ -75,4 +78,19 @@ public FirebaseAuth defaultFirebaseAuth(FirebaseApp firebaseApp) { public FirebaseMessaging defaultFirebaseMessaging(FirebaseApp firebaseApp) { return FirebaseMessaging.getInstance(firebaseApp); } + + @Bean + @ConditionalOnBean(name = "defaultFirebaseApp") + @ConditionalOnBooleanProperty(prefix = PREFIX + ".apps.default.storage", value = "enabled", matchIfMissing = true) + @ConditionalOnProperty(prefix = PREFIX, value = "apps.default.storage.bucket") + public StorageClient defaultStorageClient(FirebaseApp firebaseApp) { + return StorageClient.getInstance(firebaseApp); + } + + @Bean + @ConditionalOnBean(name = "defaultFirebaseApp") + @ConditionalOnBooleanProperty(prefix = PREFIX + ".apps.default.firestore", value = "enabled") + public Firestore defaultFirestore(FirebaseApp firebaseApp) { + return FirestoreClient.getFirestore(firebaseApp); + } } diff --git a/firebase-spring-boot/src/test/java/io/github/justedlev/firebase/FirebaseAutoConfigurationTest.java b/firebase-spring-boot/src/test/java/io/github/justedlev/firebase/FirebaseAutoConfigurationTest.java index 123d21e..f2f5cd4 100644 --- a/firebase-spring-boot/src/test/java/io/github/justedlev/firebase/FirebaseAutoConfigurationTest.java +++ b/firebase-spring-boot/src/test/java/io/github/justedlev/firebase/FirebaseAutoConfigurationTest.java @@ -1,7 +1,9 @@ package io.github.justedlev.firebase; +import com.google.cloud.firestore.Firestore; import com.google.firebase.FirebaseApp; import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.cloud.StorageClient; import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.messaging.FirebaseMessaging; import io.github.justedlev.firebase.autoconfigure.FirebaseAutoConfiguration; @@ -74,31 +76,19 @@ void whenAllEnabled_thenBeansCreated() { .withPropertyValues( "firebase.enabled=true", "firebase.apps.default.project-id=default-project", - "firebase.apps.default.database-url=http://localhost:0", + "firebase.apps.default.db.url=http://localhost:0", "firebase.apps.default.auth.enabled=true", - "firebase.apps.default.messaging.enabled=true" + "firebase.apps.default.messaging.enabled=true", + "firebase.apps.default.storage.bucket=default-bucket", + "firebase.apps.default.firestore.enabled=true" ) .run(context -> { assertThat(context).hasSingleBean(FirebaseApp.class); assertThat(context).hasSingleBean(FirebaseDatabase.class); assertThat(context).hasSingleBean(FirebaseAuth.class); assertThat(context).hasSingleBean(FirebaseMessaging.class); - }); - } - - @Test - void whenDatabaseUrlIsSet_thenFirebaseDatabaseBeanCreated() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(FirebaseAutoConfiguration.class)) - .withUserConfiguration(FirebaseTestConfiguration.class) - .withPropertyValues( - "firebase.enabled=true", - "firebase.apps.default.project-id=default-project", - "firebase.apps.default.database-url=http://localhost:0" - ) - .run(context -> { - assertThat(context).hasSingleBean(FirebaseApp.class); - assertThat(context).hasSingleBean(FirebaseDatabase.class); + assertThat(context).hasSingleBean(StorageClient.class); + assertThat(context).hasSingleBean(Firestore.class); }); } From 14370f39bee8a5116dcd39ab30815cc31f1acdc7 Mon Sep 17 00:00:00 2001 From: "Edward L." Date: Sat, 23 May 2026 20:31:19 +0300 Subject: [PATCH 5/8] refactor(config): use `gcloud` default cred path --- firebase-spring-boot/src/main/resources/application.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/firebase-spring-boot/src/main/resources/application.yaml b/firebase-spring-boot/src/main/resources/application.yaml index ee70d79..1c66f17 100644 --- a/firebase-spring-boot/src/main/resources/application.yaml +++ b/firebase-spring-boot/src/main/resources/application.yaml @@ -1,5 +1,4 @@ firebase: apps: default: - database-url: https://${firebase.apps.default.project-id}-default-rtdb.firebaseio.com - credentials: file:${user.home}/.firebase/${firebase.apps.default.project-id}.json + credentials: file:${user.home}/.config/gcloud/${firebase.apps.default.project-id}.json From 8dbb3232dfbde8772c62a2d35071416b7ac17f80 Mon Sep 17 00:00:00 2001 From: "Edward L." Date: Sat, 23 May 2026 20:34:22 +0300 Subject: [PATCH 6/8] feat(config): use google application default creds on empty property --- .../justedlev/firebase/DefaultFirebaseCredentialsFactory.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseCredentialsFactory.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseCredentialsFactory.java index 8a704bd..94eede6 100644 --- a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseCredentialsFactory.java +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/DefaultFirebaseCredentialsFactory.java @@ -3,11 +3,15 @@ import com.google.auth.oauth2.GoogleCredentials; import lombok.SneakyThrows; import org.springframework.core.io.ResourceLoader; +import org.springframework.util.StringUtils; public record DefaultFirebaseCredentialsFactory(ResourceLoader resourceLoader) implements FirebaseCredentialsFactory { @SneakyThrows @Override public GoogleCredentials create(FirebaseProperties properties) { + if (!StringUtils.hasText(properties.getCredentials())) { + return GoogleCredentials.getApplicationDefault(); + } var resource = resourceLoader.getResource(properties.getCredentials()); var in = resource.getInputStream(); return GoogleCredentials.fromStream(in); From 570ae7fd8f296ad1764b3802250f9a0e2adf5edd Mon Sep 17 00:00:00 2001 From: "Edward L." Date: Fri, 5 Jun 2026 11:27:23 +0300 Subject: [PATCH 7/8] refactor(config): improve code Signed-off-by: Edward L. --- .../FirebaseAppsConfiguredCondition.java | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAppsConfiguredCondition.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAppsConfiguredCondition.java index 9ebfcf1..c088e8d 100644 --- a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAppsConfiguredCondition.java +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/autoconfigure/FirebaseAppsConfiguredCondition.java @@ -1,6 +1,5 @@ package io.github.justedlev.firebase.autoconfigure; -import io.github.justedlev.firebase.FirebaseProperties; import io.github.justedlev.firebase.config.FirebaseConfigurationProperties; import org.jspecify.annotations.NonNull; import org.springframework.boot.autoconfigure.condition.ConditionMessage; @@ -9,18 +8,18 @@ import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.type.AnnotatedTypeMetadata; import java.util.Collections; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; -import java.util.stream.Collectors; class FirebaseAppsConfiguredCondition extends SpringBootCondition { - private static final Bindable<@NonNull Map> BINDABLE = - Bindable.mapOf(String.class, FirebaseProperties.class); + private static final String MATCH_MSG = "registered firebase apps"; + private static final Bindable<@NonNull FirebaseConfigurationProperties> BINDABLE = + Bindable.of(FirebaseConfigurationProperties.class); @NonNull @Override @@ -28,23 +27,26 @@ public ConditionOutcome getMatchOutcome(@NonNull ConditionContext context, @NonN var exclude = getExclude(metadata); var message = ConditionMessage.forCondition("Firebase Apps Configured Condition"); var appNames = Binder.get(context.getEnvironment()) - .bind(FirebaseConfigurationProperties.PREFIX + ".apps", BINDABLE) - .orElse(Collections.emptyMap()) + .bind(FirebaseConfigurationProperties.PREFIX, BINDABLE) + .orElseGet(FirebaseConfigurationProperties::new) + .getApps() .keySet() .stream() .filter(Predicate.not(exclude::contains)) - .collect(Collectors.joining(", ")); - if (!appNames.isEmpty()) { - return ConditionOutcome.match(message.foundExactly("registered firebase apps " + appNames)); + .toList(); + + if (appNames.isEmpty()) { + return ConditionOutcome.noMatch(message.notAvailable(MATCH_MSG)); } - return ConditionOutcome.noMatch(message.notAvailable("registered firebase apps")); + + return ConditionOutcome.match(message.found(MATCH_MSG).items(ConditionMessage.Style.QUOTE, appNames)); } private Set getExclude(AnnotatedTypeMetadata metadata) { - return Optional.of(metadata) - .map(v -> v.getAnnotationAttributes(ConditionalOnFirebaseAppsProperties.class.getName())) - .map(v -> v.get("exclude")) - .map(String[].class::cast) + return Optional.of(metadata.getAnnotations()) + .map(v -> v.get(ConditionalOnFirebaseAppsProperties.class)) + .map(MergedAnnotation::synthesize) + .map(ConditionalOnFirebaseAppsProperties::exclude) .map(Set::of) .orElseGet(Collections::emptySet); } From 8e048b1f44c09d019672bace4fd3fbd9c82f8bca Mon Sep 17 00:00:00 2001 From: "Edward L." Date: Fri, 5 Jun 2026 11:37:41 +0300 Subject: [PATCH 8/8] refactor: add `@lombok.Builder.Default` on fields Signed-off-by: Edward L. --- .../io/github/justedlev/firebase/FirebaseProperties.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseProperties.java b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseProperties.java index 272eb60..ba821ab 100644 --- a/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseProperties.java +++ b/firebase-spring-boot/src/main/java/io/github/justedlev/firebase/FirebaseProperties.java @@ -27,9 +27,14 @@ public class FirebaseProperties { private Integer connectTimeout; private Integer readTimeout; private Integer writeTimeout; + @lombok.Builder.Default private FirebaseAuthProperties auth = new FirebaseAuthProperties(); + @lombok.Builder.Default private FirebaseDatabaseProperties db = new FirebaseDatabaseProperties(); + @lombok.Builder.Default private FirebaseMessagingProperties messaging = new FirebaseMessagingProperties(); + @lombok.Builder.Default private StorageProperties storage = new StorageProperties(); + @lombok.Builder.Default private FirestoreProperties firestore = new FirestoreProperties(); }