diff --git a/src/main/java/tech/jhipster/lite/generator/server/javatool/protobuf/application/ProtobufApplicationService.java b/src/main/java/tech/jhipster/lite/generator/server/javatool/protobuf/application/ProtobufApplicationService.java
new file mode 100644
index 00000000000..0eee6035d98
--- /dev/null
+++ b/src/main/java/tech/jhipster/lite/generator/server/javatool/protobuf/application/ProtobufApplicationService.java
@@ -0,0 +1,24 @@
+package tech.jhipster.lite.generator.server.javatool.protobuf.application;
+
+import org.springframework.stereotype.Service;
+import tech.jhipster.lite.generator.server.javatool.protobuf.domain.ProtobufModuleFactory;
+import tech.jhipster.lite.module.domain.JHipsterModule;
+import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties;
+
+@Service
+public class ProtobufApplicationService {
+
+ private final ProtobufModuleFactory factory;
+
+ public ProtobufApplicationService() {
+ factory = new ProtobufModuleFactory();
+ }
+
+ public JHipsterModule buildProtobufModule(JHipsterModuleProperties properties) {
+ return factory.buildProtobufModule(properties);
+ }
+
+ public JHipsterModule buildProtobufBackwardsCompatibilityCheckModule(JHipsterModuleProperties properties) {
+ return factory.buildProtobufBackwardsCompatibilityCheckModule(properties);
+ }
+}
diff --git a/src/main/java/tech/jhipster/lite/generator/server/javatool/protobuf/domain/ProtobufModuleFactory.java b/src/main/java/tech/jhipster/lite/generator/server/javatool/protobuf/domain/ProtobufModuleFactory.java
new file mode 100644
index 00000000000..d8052c1b2bb
--- /dev/null
+++ b/src/main/java/tech/jhipster/lite/generator/server/javatool/protobuf/domain/ProtobufModuleFactory.java
@@ -0,0 +1,120 @@
+package tech.jhipster.lite.generator.server.javatool.protobuf.domain;
+
+import static tech.jhipster.lite.module.domain.JHipsterModule.*;
+
+import tech.jhipster.lite.module.domain.JHipsterModule;
+import tech.jhipster.lite.module.domain.file.JHipsterDestination;
+import tech.jhipster.lite.module.domain.file.JHipsterSource;
+import tech.jhipster.lite.module.domain.gradleplugin.GradleCommunityPlugin;
+import tech.jhipster.lite.module.domain.javabuild.GroupId;
+import tech.jhipster.lite.module.domain.javabuild.VersionSlug;
+import tech.jhipster.lite.module.domain.mavenplugin.MavenPlugin;
+import tech.jhipster.lite.module.domain.mavenplugin.MavenPlugin.MavenPluginOptionalBuilder;
+import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties;
+import tech.jhipster.lite.shared.error.domain.Assert;
+
+public class ProtobufModuleFactory {
+
+ private static final JHipsterSource SOURCE = from("server/javatool/protobuf");
+ private static final JHipsterSource MAIN_SOURCE = SOURCE.append("main");
+ private static final JHipsterSource TEST_SOURCE = SOURCE.append("test");
+
+ private static final String PROTOBUF_PACKAGE = "shared/protobuf";
+ private static final VersionSlug PROTOBUF_VERSION_SLUG = versionSlug("protobuf");
+ private static final GroupId PROTOBUF_GROUPID = groupId("com.google.protobuf");
+
+ public JHipsterModule buildProtobufModule(JHipsterModuleProperties properties) {
+ Assert.notNull("properties", properties);
+
+ JHipsterDestination mainDestination = toSrcMainJava().append(properties.packagePath()).append(PROTOBUF_PACKAGE);
+ JHipsterDestination testDestination = toSrcTestJava().append(properties.packagePath()).append(PROTOBUF_PACKAGE);
+
+ //@formatter:off
+ return moduleBuilder(properties)
+ .files()
+ .add(MAIN_SOURCE.template("package-info.java"), mainDestination.append("package-info.java"))
+ .add(MAIN_SOURCE.template("ProtobufDatesReader.java"), mainDestination.append("infrastructure/primary/ProtobufDatesReader.java"))
+ .add(MAIN_SOURCE.template("ProtobufDatesWriter.java"), mainDestination.append("infrastructure/secondary/ProtobufDatesWriter.java"))
+ .add(MAIN_SOURCE.append(".gitkeep"), to("src/main/proto/.gitkeep"))
+ .add(
+ TEST_SOURCE.template("ProtobufDatesReaderTest.java"),
+ testDestination.append("infrastructure/primary/ProtobufDatesReaderTest.java")
+ )
+ .add(
+ TEST_SOURCE.template("ProtobufDatesWriterTest.java"),
+ testDestination.append("infrastructure/secondary/ProtobufDatesWriterTest.java")
+ )
+ .and()
+ .javaDependencies()
+ .addDependency(PROTOBUF_GROUPID, artifactId("protobuf-java"), PROTOBUF_VERSION_SLUG)
+ .addTestDependency(PROTOBUF_GROUPID, artifactId("protobuf-java-util"), PROTOBUF_VERSION_SLUG)
+ .and()
+ .mavenPlugins()
+ .pluginManagement(protobufMavenPluginManagement())
+ .plugin(protobufMavenPluginBuilder().build())
+ .and()
+ .mavenBuildExtensions()
+ .addExtension(groupId("kr.motd.maven"), artifactId("os-maven-plugin"), versionSlug("os-maven-plugin"))
+ .and()
+ .gradlePlugins()
+ .plugin(protobufGradlePlugin())
+ .and()
+ .build();
+ //@formatter:on
+ }
+
+ private static GradleCommunityPlugin protobufGradlePlugin() {
+ return gradleCommunityPlugin()
+ .id("com.google.protobuf")
+ .pluginSlug("protobuf")
+ .versionSlug("protobuf-plugin")
+ .configuration(
+ """
+ protobuf {
+ protoc {
+ artifact = "com.google.protobuf:protoc:${libs.versions.protobuf.asProvider().get()}"
+ }
+ }
+ """
+ )
+ .build();
+ }
+
+ private MavenPlugin protobufMavenPluginManagement() {
+ return protobufMavenPluginBuilder()
+ .versionSlug("protobuf-maven-plugin")
+ .configuration(
+ """
+ com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
+ """
+ )
+ .addExecution(pluginExecution().goals("compile"))
+ .build();
+ }
+
+ private MavenPluginOptionalBuilder protobufMavenPluginBuilder() {
+ return mavenPlugin().groupId("org.xolstice.maven.plugins").artifactId("protobuf-maven-plugin");
+ }
+
+ public JHipsterModule buildProtobufBackwardsCompatibilityCheckModule(JHipsterModuleProperties properties) {
+ //@formatter:off
+ return moduleBuilder(properties)
+ .mavenPlugins()
+ .pluginManagement(protoBackwardsCompatibilityMavenPluginManagement())
+ .plugin(protoBackwardsCompatibilityMavenPluginBuilder().build())
+ .and()
+ .build();
+ //@formatter:on
+ }
+
+ private MavenPlugin protoBackwardsCompatibilityMavenPluginManagement() {
+ return protoBackwardsCompatibilityMavenPluginBuilder()
+ .versionSlug("proto-backwards-compatibility")
+ .addExecution(pluginExecution().goals("backwards-compatibility-check"))
+ .build();
+ }
+
+ private MavenPluginOptionalBuilder protoBackwardsCompatibilityMavenPluginBuilder() {
+ return mavenPlugin().groupId("com.salesforce.servicelibs").artifactId("proto-backwards-compatibility");
+ }
+}
diff --git a/src/main/java/tech/jhipster/lite/generator/server/javatool/protobuf/infrastructure/primary/ProtobufModuleConfiguration.java b/src/main/java/tech/jhipster/lite/generator/server/javatool/protobuf/infrastructure/primary/ProtobufModuleConfiguration.java
new file mode 100644
index 00000000000..3b0ed1eb640
--- /dev/null
+++ b/src/main/java/tech/jhipster/lite/generator/server/javatool/protobuf/infrastructure/primary/ProtobufModuleConfiguration.java
@@ -0,0 +1,40 @@
+package tech.jhipster.lite.generator.server.javatool.protobuf.infrastructure.primary;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import tech.jhipster.lite.generator.server.javatool.protobuf.application.ProtobufApplicationService;
+import tech.jhipster.lite.generator.slug.domain.JHLiteFeatureSlug;
+import tech.jhipster.lite.generator.slug.domain.JHLiteModuleSlug;
+import tech.jhipster.lite.module.domain.resource.JHipsterModuleOrganization;
+import tech.jhipster.lite.module.domain.resource.JHipsterModulePropertiesDefinition;
+import tech.jhipster.lite.module.domain.resource.JHipsterModuleResource;
+
+@Configuration
+class ProtobufModuleConfiguration {
+
+ @Bean
+ JHipsterModuleResource protobufModule(ProtobufApplicationService protobuf) {
+ return JHipsterModuleResource
+ .builder()
+ .slug(JHLiteModuleSlug.PROTOBUF)
+ .propertiesDefinition(JHipsterModulePropertiesDefinition.builder().addBasePackage().addIndentation().build())
+ .apiDoc("Java", "Add protobuf support")
+ .organization(JHipsterModuleOrganization.builder().addDependency(JHLiteFeatureSlug.JAVA_BUILD_TOOL).build())
+ .tags("server", "protobuf")
+ .factory(protobuf::buildProtobufModule);
+ }
+
+ @Bean
+ JHipsterModuleResource protobufBackwardsCompatibilityCheckModule(ProtobufApplicationService protobuf) {
+ return JHipsterModuleResource
+ .builder()
+ .slug(JHLiteModuleSlug.PROTOBUF_BACKWARDS_COMPATIBILITY_CHECK)
+ .propertiesDefinition(JHipsterModulePropertiesDefinition.builder().addBasePackage().addIndentation().build())
+ .apiDoc("Java", "Add protobuf backwards compatibility check")
+ .organization(
+ JHipsterModuleOrganization.builder().addDependency(JHLiteModuleSlug.PROTOBUF).addDependency(JHLiteModuleSlug.MAVEN_JAVA).build()
+ )
+ .tags("server", "protobuf")
+ .factory(protobuf::buildProtobufBackwardsCompatibilityCheckModule);
+ }
+}
diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/dummy/feature/domain/DummyFeatureModuleFactory.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/dummy/feature/domain/DummyFeatureModuleFactory.java
index 6e1d283ef45..c3168a1e561 100644
--- a/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/dummy/feature/domain/DummyFeatureModuleFactory.java
+++ b/src/main/java/tech/jhipster/lite/generator/server/springboot/mvc/dummy/feature/domain/DummyFeatureModuleFactory.java
@@ -39,9 +39,6 @@ public JHipsterModule buildModule(JHipsterModuleProperties properties) {
.put("baseName", properties.projectBaseName().capitalized())
.and()
.documentation(documentationTitle("Dummy"), SOURCE.file("dummy.md"))
- .mavenBuildExtensions()
- .addExtension(groupId("kr.motd.maven"), artifactId("os-maven-plugin"), versionSlug("os-maven-plugin"))
- .and()
.files()
.batch(MAIN_SOURCE, mainDestination)
.addTemplate("package-info.java")
diff --git a/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java b/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java
index 87f7eda92a8..8122d5aa852 100644
--- a/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java
+++ b/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java
@@ -80,6 +80,8 @@ public enum JHLiteModuleSlug implements JHipsterModuleSlugFactory {
PLAYWRIGHT("playwright"),
POSTGRESQL("postgresql"),
PRETTIER("prettier"),
+ PROTOBUF("protobuf"),
+ PROTOBUF_BACKWARDS_COMPATIBILITY_CHECK("protobuf-backwards-compatibility-check"),
REACT_CORE("react-core"),
REACT_JWT("react-jwt"),
REST_PAGINATION("rest-pagination"),
diff --git a/src/main/java/tech/jhipster/lite/module/domain/javadependency/JHipsterModuleJavaDependencies.java b/src/main/java/tech/jhipster/lite/module/domain/javadependency/JHipsterModuleJavaDependencies.java
index 32efa997f0f..f2855e8d0c4 100644
--- a/src/main/java/tech/jhipster/lite/module/domain/javadependency/JHipsterModuleJavaDependencies.java
+++ b/src/main/java/tech/jhipster/lite/module/domain/javadependency/JHipsterModuleJavaDependencies.java
@@ -1,5 +1,7 @@
package tech.jhipster.lite.module.domain.javadependency;
+import static tech.jhipster.lite.module.domain.javadependency.JavaDependencyScope.TEST;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Function;
@@ -124,6 +126,18 @@ public JHipsterModuleJavaDependenciesBuilder addDependency(GroupId groupId, Arti
return addDependency(dependency);
}
+ public JHipsterModuleJavaDependenciesBuilder addTestDependency(GroupId groupId, ArtifactId artifactId, VersionSlug versionSlug) {
+ JavaDependency dependency = JavaDependency
+ .builder()
+ .groupId(groupId)
+ .artifactId(artifactId)
+ .versionSlug(versionSlug)
+ .scope(TEST)
+ .build();
+
+ return addDependency(dependency);
+ }
+
public JHipsterModuleJavaDependenciesBuilder addDependency(JavaDependency dependency) {
Assert.notNull(DEPENDENCY, dependency);
diff --git a/src/main/java/tech/jhipster/lite/module/domain/replacement/RegexNeedleAfterReplacer.java b/src/main/java/tech/jhipster/lite/module/domain/replacement/RegexNeedleAfterReplacer.java
index 1f4c55afc50..8340d422d8c 100644
--- a/src/main/java/tech/jhipster/lite/module/domain/replacement/RegexNeedleAfterReplacer.java
+++ b/src/main/java/tech/jhipster/lite/module/domain/replacement/RegexNeedleAfterReplacer.java
@@ -19,7 +19,13 @@ public boolean notMatchIn(String content) {
@Override
public BiFunction replacement() {
return (content, replacement) ->
- linePattern().matcher(content).replaceAll(result -> result.group() + JHipsterModule.LINE_BREAK + replacement);
+ linePattern()
+ .matcher(content)
+ .replaceAll(result -> result.group() + JHipsterModule.LINE_BREAK + escapeSpecialCharacters(replacement));
+ }
+
+ private String escapeSpecialCharacters(String replacement) {
+ return replacement.replace("$", "\\$");
}
private Pattern linePattern() {
diff --git a/src/main/java/tech/jhipster/lite/module/domain/replacement/RegexNeedleBeforeReplacer.java b/src/main/java/tech/jhipster/lite/module/domain/replacement/RegexNeedleBeforeReplacer.java
index 4cb3ee299d7..190952cd3bf 100644
--- a/src/main/java/tech/jhipster/lite/module/domain/replacement/RegexNeedleBeforeReplacer.java
+++ b/src/main/java/tech/jhipster/lite/module/domain/replacement/RegexNeedleBeforeReplacer.java
@@ -19,7 +19,13 @@ public boolean notMatchIn(String content) {
@Override
public BiFunction replacement() {
return (content, replacement) ->
- linePattern().matcher(content).replaceAll(result -> replacement + JHipsterModule.LINE_BREAK + result.group());
+ linePattern()
+ .matcher(content)
+ .replaceAll(result -> escapeSpecialCharacters(replacement) + JHipsterModule.LINE_BREAK + result.group());
+ }
+
+ private String escapeSpecialCharacters(String replacement) {
+ return replacement.replace("$", "\\$");
}
private Pattern linePattern() {
diff --git a/src/main/resources/generator/dependencies/build.gradle.kts b/src/main/resources/generator/dependencies/build.gradle.kts
index 0b1686e7a21..6b24fe71622 100644
--- a/src/main/resources/generator/dependencies/build.gradle.kts
+++ b/src/main/resources/generator/dependencies/build.gradle.kts
@@ -2,6 +2,7 @@ plugins {
java
checkstyle
alias(libs.plugins.jib)
+ alias(libs.plugins.protobuf)
// jhipster-needle-gradle-plugin
}
diff --git a/src/main/resources/generator/dependencies/gradle/libs.versions.toml b/src/main/resources/generator/dependencies/gradle/libs.versions.toml
index a72e9d1af92..14059735273 100644
--- a/src/main/resources/generator/dependencies/gradle/libs.versions.toml
+++ b/src/main/resources/generator/dependencies/gradle/libs.versions.toml
@@ -2,6 +2,7 @@
checkstyle = "10.12.7"
jib = "3.4.0"
git-properties = "2.4.1"
+protobuf-plugin = "0.9.4"
[libraries]
[libraries.checkstyle]
@@ -17,3 +18,7 @@ version.ref = "jib"
[plugins.git-properties]
id = "com.gorylenko.gradle-git-properties"
version.ref = "git-properties"
+
+[plugins.protobuf]
+id = "com.google.cloud.tools.jib"
+version.ref = "protobuf-plugin"
diff --git a/src/main/resources/generator/dependencies/pom.xml b/src/main/resources/generator/dependencies/pom.xml
index d12c58147aa..507b95809a7 100644
--- a/src/main/resources/generator/dependencies/pom.xml
+++ b/src/main/resources/generator/dependencies/pom.xml
@@ -54,6 +54,9 @@
1.9.10
3.13.3
1.7.1
+ 3.23.3
+ 0.6.1
+ 1.0.7
@@ -305,6 +308,21 @@
neo4j-migrations
${neo4j-migrations.version}
+
+ com.google.protobuf
+ protobuf-java
+ ${protobuf.version}
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+ ${protobuf-maven-plugin.version}
+
+
+ com.salesforce.servicelibs
+ proto-backwards-compatibility
+ ${proto-backwards-compatibility.version}
+
diff --git a/src/main/resources/generator/server/javatool/protobuf/main/.gitkeep b/src/main/resources/generator/server/javatool/protobuf/main/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/main/resources/generator/server/javatool/protobuf/main/ProtobufDatesReader.java.mustache b/src/main/resources/generator/server/javatool/protobuf/main/ProtobufDatesReader.java.mustache
new file mode 100644
index 00000000000..19073f6c499
--- /dev/null
+++ b/src/main/resources/generator/server/javatool/protobuf/main/ProtobufDatesReader.java.mustache
@@ -0,0 +1,17 @@
+package {{packageName}}.shared.protobuf.infrastructure.primary;
+
+import com.google.protobuf.Timestamp;
+import java.time.Instant;
+
+public final class ProtobufDatesReader {
+
+ private ProtobufDatesReader() {}
+
+ public static Instant readInstant(Timestamp timestamp) {
+ if (timestamp == null || timestamp.getSeconds() == 0) {
+ return null;
+ }
+
+ return Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos());
+ }
+}
diff --git a/src/main/resources/generator/server/javatool/protobuf/main/ProtobufDatesWriter.java.mustache b/src/main/resources/generator/server/javatool/protobuf/main/ProtobufDatesWriter.java.mustache
new file mode 100644
index 00000000000..a8b20ecb43a
--- /dev/null
+++ b/src/main/resources/generator/server/javatool/protobuf/main/ProtobufDatesWriter.java.mustache
@@ -0,0 +1,17 @@
+package {{packageName}}.shared.protobuf.infrastructure.secondary;
+
+import com.google.protobuf.Timestamp;
+import java.time.Instant;
+
+public final class ProtobufDatesWriter {
+
+ private ProtobufDatesWriter() {}
+
+ public static Timestamp buildTimestamp(Instant instant) {
+ if (instant == null) {
+ return null;
+ }
+
+ return Timestamp.newBuilder().setSeconds(instant.getEpochSecond()).setNanos(instant.getNano()).build();
+ }
+}
diff --git a/src/main/resources/generator/server/javatool/protobuf/main/package-info.java.mustache b/src/main/resources/generator/server/javatool/protobuf/main/package-info.java.mustache
new file mode 100644
index 00000000000..3034d3b2de9
--- /dev/null
+++ b/src/main/resources/generator/server/javatool/protobuf/main/package-info.java.mustache
@@ -0,0 +1,2 @@
+@{{packageName}}.SharedKernel
+package {{packageName}}.shared.protobuf;
diff --git a/src/main/resources/generator/server/javatool/protobuf/test/ProtobufDatesReaderTest.java.mustache b/src/main/resources/generator/server/javatool/protobuf/test/ProtobufDatesReaderTest.java.mustache
new file mode 100644
index 00000000000..0e23547ed64
--- /dev/null
+++ b/src/main/resources/generator/server/javatool/protobuf/test/ProtobufDatesReaderTest.java.mustache
@@ -0,0 +1,29 @@
+package {{packageName}}.shared.protobuf.infrastructure.primary;
+
+import static org.assertj.core.api.Assertions.*;
+
+import com.google.protobuf.util.Timestamps;
+import {{packageName}}.UnitTest;
+import java.util.Date;
+import org.junit.jupiter.api.Test;
+
+@UnitTest
+class ProtobufDatesReaderTest {
+
+ @Test
+ void shouldConvertNullTimestampToNull() {
+ assertThat(ProtobufDatesReader.readInstant(null)).isNull();
+ }
+
+ @Test
+ void shouldConvertDefaultDateToNull() {
+ assertThat(ProtobufDatesReader.readInstant(Timestamps.fromDate(new Date(0)))).isNull();
+ }
+
+ @Test
+ void shouldGetInstantFromTimestampFromDate() {
+ Date date = new Date();
+
+ assertThat(ProtobufDatesReader.readInstant(Timestamps.fromDate(date))).isEqualTo(date.toInstant());
+ }
+}
diff --git a/src/main/resources/generator/server/javatool/protobuf/test/ProtobufDatesWriterTest.java.mustache b/src/main/resources/generator/server/javatool/protobuf/test/ProtobufDatesWriterTest.java.mustache
new file mode 100644
index 00000000000..8aee064bb22
--- /dev/null
+++ b/src/main/resources/generator/server/javatool/protobuf/test/ProtobufDatesWriterTest.java.mustache
@@ -0,0 +1,23 @@
+package {{packageName}}.shared.protobuf.infrastructure.secondary;
+
+import static org.assertj.core.api.Assertions.*;
+
+import com.google.protobuf.Timestamp;
+import {{packageName}}.UnitTest;
+import java.time.Instant;
+import org.junit.jupiter.api.Test;
+
+@UnitTest
+class ProtobufDatesWriterTest {
+
+ @Test
+ void shouldBuildNullTimestampFromNullInstant() {
+ assertThat(ProtobufDatesWriter.buildTimestamp(null)).isNull();
+ }
+
+ @Test
+ void shouldBuildTimestampFromInstant() {
+ assertThat(ProtobufDatesWriter.buildTimestamp(Instant.ofEpochMilli(1337)))
+ .isEqualTo(Timestamp.newBuilder().setSeconds(1).setNanos(337000000).build());
+ }
+}
diff --git a/src/test/features/server/javatool/protobuf.feature b/src/test/features/server/javatool/protobuf.feature
new file mode 100644
index 00000000000..bbb23d11f45
--- /dev/null
+++ b/src/test/features/server/javatool/protobuf.feature
@@ -0,0 +1,16 @@
+Feature: Protobuf module
+
+ Scenario: Should apply protobuf module
+ When I apply modules to default project
+ | maven-java |
+ | protobuf |
+ Then I should have files in "src/main/java/tech/jhipster/chips/shared/protobuf/infrastructure"
+ | primary/ProtobufDatesReader.java |
+ | secondary/ProtobufDatesWriter.java |
+
+ Scenario: Should apply protobuf backwards compatibility check module
+ When I apply modules to default project
+ | maven-java |
+ | protobuf |
+ | protobuf-backwards-compatibility-check |
+ Then I should have "proto-backwards-compatibility" in "pom.xml"
diff --git a/src/test/java/tech/jhipster/lite/generator/server/javatool/protobuf/domain/ProtobufModuleFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/server/javatool/protobuf/domain/ProtobufModuleFactoryTest.java
new file mode 100644
index 00000000000..736f0a03035
--- /dev/null
+++ b/src/test/java/tech/jhipster/lite/generator/server/javatool/protobuf/domain/ProtobufModuleFactoryTest.java
@@ -0,0 +1,183 @@
+package tech.jhipster.lite.generator.server.javatool.protobuf.domain;
+
+import static tech.jhipster.lite.module.infrastructure.secondary.JHipsterModulesAssertions.*;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import tech.jhipster.lite.TestFileUtils;
+import tech.jhipster.lite.UnitTest;
+import tech.jhipster.lite.module.domain.JHipsterModule;
+import tech.jhipster.lite.module.domain.JHipsterModulesFixture;
+import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties;
+
+@UnitTest
+class ProtobufModuleFactoryTest {
+
+ private static final ProtobufModuleFactory factory = new ProtobufModuleFactory();
+
+ @Nested
+ class ProtobufModule {
+
+ @Test
+ void shouldBuildModuleForMaven() {
+ JHipsterModuleProperties properties = JHipsterModulesFixture
+ .propertiesBuilder(TestFileUtils.tmpDirForTest())
+ .basePackage("com.jhipster.test")
+ .build();
+
+ JHipsterModule module = factory.buildProtobufModule(properties);
+
+ assertThatModuleWithFiles(module, pomFile())
+ .hasPrefixedFiles(
+ "src/main/java/com/jhipster/test/shared/protobuf",
+ "package-info.java",
+ "infrastructure/primary/ProtobufDatesReader.java",
+ "infrastructure/secondary/ProtobufDatesWriter.java"
+ )
+ .hasPrefixedFiles(
+ "src/test/java/com/jhipster/test/shared/protobuf",
+ "infrastructure/primary/ProtobufDatesReaderTest.java",
+ "infrastructure/secondary/ProtobufDatesWriterTest.java"
+ )
+ .hasFiles("src/main/proto/.gitkeep")
+ .hasFile("pom.xml")
+ .containing(
+ """
+
+ com.google.protobuf
+ protobuf-java
+ ${protobuf.version}
+
+ """
+ )
+ .containing(
+ """
+
+ com.google.protobuf
+ protobuf-java-util
+ ${protobuf.version}
+ test
+
+ """
+ )
+ .containing(
+ """
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+ ${protobuf-maven-plugin.version}
+
+
+
+ compile
+
+
+
+
+ com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
+
+
+ """
+ )
+ .containing(
+ """
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+
+ """
+ )
+ .containing(
+ """
+
+
+
+ kr.motd.maven
+ os-maven-plugin
+ ${os-maven-plugin.version}
+
+
+ """
+ );
+ }
+
+ @Test
+ void shouldBuildModuleForGradle() {
+ JHipsterModuleProperties properties = JHipsterModulesFixture.propertiesBuilder(TestFileUtils.tmpDirForTest()).build();
+
+ JHipsterModule module = factory.buildProtobufModule(properties);
+
+ assertThatModuleWithFiles(module, gradleBuildFile(), gradleLibsVersionFile())
+ .hasFile("gradle/libs.versions.toml")
+ .containing("protobuf = \"")
+ .containing("protobuf-plugin = \"")
+ .containing(
+ """
+ \t[plugins.protobuf]
+ \t\tid = "com.google.protobuf"
+
+ \t\t[plugins.protobuf.version]
+ \t\t\tref = "protobuf-plugin"
+ """
+ )
+ .and()
+ .hasFile("build.gradle.kts")
+ .containing(
+ """
+ alias(libs.plugins.protobuf)
+ // jhipster-needle-gradle-plugins
+ """
+ )
+ .containing(
+ """
+ protobuf {
+ protoc {
+ artifact = "com.google.protobuf:protoc:${libs.versions.protobuf.asProvider().get()}"
+ }
+ }
+ """
+ );
+ }
+ }
+
+ @Nested
+ class ProtobufBackwardsCompatibilityCheckModule {
+
+ @Test
+ void shouldBuildModuleForMaven() {
+ JHipsterModuleProperties properties = JHipsterModulesFixture
+ .propertiesBuilder(TestFileUtils.tmpDirForTest())
+ .basePackage("com.jhipster.test")
+ .build();
+
+ JHipsterModule module = factory.buildProtobufBackwardsCompatibilityCheckModule(properties);
+
+ assertThatModuleWithFiles(module, pomFile())
+ .hasFile("pom.xml")
+ .containing(
+ """
+
+ com.salesforce.servicelibs
+ proto-backwards-compatibility
+ ${proto-backwards-compatibility.version}
+
+
+
+ backwards-compatibility-check
+
+
+
+
+ """
+ )
+ .containing(
+ """
+
+ com.salesforce.servicelibs
+ proto-backwards-compatibility
+
+ """
+ );
+ }
+ }
+}
diff --git a/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/dummy/feature/domain/DummyFeatureModuleFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/dummy/feature/domain/DummyFeatureModuleFactoryTest.java
index 44a493542aa..17b58b97227 100644
--- a/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/dummy/feature/domain/DummyFeatureModuleFactoryTest.java
+++ b/src/test/java/tech/jhipster/lite/generator/server/springboot/mvc/dummy/feature/domain/DummyFeatureModuleFactoryTest.java
@@ -26,15 +26,6 @@ void shouldBuildModule() {
assertThatModuleWithFiles(module, pomFile())
.hasFile("pom.xml")
- .containing(
- """
-
- kr.motd.maven
- os-maven-plugin
- ${os-maven-plugin.version}
-
- """
- )
.and()
.hasFiles("documentation/dummy.md")
.hasPrefixedFiles("src/main/java/com/jhipster/test/dummy", "package-info.java")
diff --git a/tests-ci/generate.sh b/tests-ci/generate.sh
index 8728f0cd303..c0b89881ee3 100755
--- a/tests-ci/generate.sh
+++ b/tests-ci/generate.sh
@@ -64,6 +64,8 @@ spring_boot() {
"github-actions" \
"java-base" \
"checkstyle" \
+ "protobuf" \
+ "protobuf-backwards-compatibility-check" \
"jacoco-check-min-coverage" \
"spring-boot" \
"logs-spy"
@@ -131,6 +133,7 @@ elif [[ $application == 'gradleapp' ]]; then
"java-memoizers" \
"java-enums" \
"jib" \
+ "protobuf" \
"pagination-domain" \
"spring-boot" \
"logs-spy" \