Custom Spring Initializer to quickly create your app

İbrahim Esen
Trendyol Tech
Published in
7 min readDec 30, 2022

Today I will tell you a story about creating a custom Spring Boot project generator that we, as Pudo team, happily use for a long time.

What is Sprint Initializr?

Spring Initializr is a project generation tool that provides an API to quickly create JVM-based projects. It is used mostly used to create brand new Spring Boot services.

VMware hosts a public Spring Initializr instance (start.spring.io) that you may already know and used many times to create a new Spring Boot project.

You can look what is behind the scenes of start.spring.io and Spring Initializr from GitHub.

Motivation

If you work on microservices architecture with multi-repo approach, that you occasionally need to create new services. The public start.spring.io generator is a great place to create new projects but you constantly have to pass in some parameters for your team’s preferences/taste and style. You may also do the same modifications for every project just after you created them.

You may also need to add some dependencies to every service you created. Tweaking the dependencies of the project would be great.

You may also need a defined project structure for your team. Some example hello world implementation, that follows your way of implementing business, already included in the generated project would be great to originate from.

To summarize, with a custom initializer, your teammates can quickly start working on a new project, without getting lost on too many parameters and choices, and focus on actual business.

Under the hood of Initializr

Above you see the general overview of an Spring Initializr instance. Main component here that contains the all generation logic is the core. UI is just a consumer of the metadata and generation APIs that core provides. Intellij IDE and Spring Boot CLI do not need the ui, they directly communicate with core APIs to do their work.

So here, we will just create our customized version of the core initializer API. Core generator may also go to the public Spring Boot Metadata url to fetch available versions for Spring Boot framework, but we may disable this feature exposing only specific set of Spring Boot versions to be selected.

Creating a Custom Initializr Core

  • First create an empty Spring Boot application with web starter. (You will probably use start.spring.io :) )
  • Add the following to dependencies and dependencyManagement sections. (You may use a newer version of initializr-bom if exists)
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.spring.initializr</groupId>
<artifactId>initializr-web</artifactId>
</dependency>
<dependency>
<groupId>io.spring.initializr</groupId>
<artifactId>initializr-generator-spring</artifactId>
</dependency>
...
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.spring.initializr</groupId>
<artifactId>initializr-bom</artifactId>
<version>0.13.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
  • You can now run the application and see a json result on http://localhost:8080 . You can see that it returns the same config as default initializer.
  • Now let’s override configuration of the initializr, editing the application.yml like below
ya:
description:
description: Project description
title: Description
value: Demo spring project for Pudo team
artifact-id:
description: Artifact Id of the project
title: Artifact Id
value: demo
group-id:
value: com.trendyol
description: Group id
title: Group id
version:
description: Project version
title: Version
value: 0-SNAPSHOT
types:
- name: Maven Project
id: maven-project
description: Generate a maven based project archive
tags:
build: maven
format: project
default: true
action: /starter.zip
packagings:
- name: Jar
id: jar
default: true
languages:
- name: Java
id: java
default: true
java-versions:
- id: 17
default: true
boot-versions:
- name: 2.7.7
id: 2.7.7
default: true
initializr:  
dependencies:
- name: Pudo
content:
- name: Pudo Api Project
id: pudo-api
groupId: com.trendyol
artifactId: pudo-api-pseudo-dependency
version: 0
description: Pudo api project
- name: Pudo Acceptance Test Project
id: pudo-acceptance
groupId: com.trendyol
artifactId: pudo-acceptance-test-pseudo-dependency
version: 0
description: Pudo acceptance test project
- name: Utilities
content:
- name: Mapstruct
id: mapstruct
groupId: org.mapstruct
artifactId: mapstruct
version: "1.5.3.Final"
description: Mapstruct object mapper library
- name: Web
content:
- name: Spring Web
id: web
  • We can group dependencies that can be selected inside content sections. While creating the project if a dependency is selected, it is added to the final build file (pom.xml, build.gradle). You can see that first two dependencies do not target real dependency coordinates. They are used as triggers to enable project customizers that I will explain below right now.
  • Spring Initializr provides more options to further customize our project output. In our initializr project, we can create custom beans implementing ProjectContributor, ProjectDescriptionCustomizer, BuildCustomizer, MainApplicationTypeCustomizer, MainCompilationUnitCustomizer, MainSourceCodeCustomizer, TestApplicationTypeCustomizer, TestSourceCodeCustomizer, HelpDocumentCustomizer, GitIgnoreCustomizer etc.
  • Beans implementing those interfaces are automatically applied if scanned and added to the application context of initializer. To conditionally apply customizations, we need to exclude them for scanning and create a special config class to enable them conditionally.
package com.trendyol.pudoinitializr;

@SpringBootApplication
@ComponentScan(excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = ProjectGenerationConfiguration.class),
@ComponentScan.Filter(type=FilterType.REGEX,
pattern="com\\.trendyol\\.pudoinitializr\\.application\\.customizations\\..*")
})
public class PudoInitializr {

public static void main(String[] args) {
SpringApplication.run(PudoInitializr.class, args);
}

}
  • Our customization beans for pudo-api pseudo dependency resides in com.trendyol.pudoinitializr.application.customizations package. Also the configuration beans, having ProjectGenerationConfiguration annotation), are excluded from scanning.
  • Conditional configuration bean can be implemented as below. Here ConditionalOnRequestedDependency annotation makes this configuration be applied only when pudo-api dependency is selected.
package com.trendyol.pudoinitializr.application.config;

@ProjectGenerationConfiguration
@ComponentScan(basePackages = "com.trendyol.pudoinitializr.application.customizations")
@ConditionalOnRequestedDependency("pudo-api")
public class ApiGeneratorsConfiguration {

}
  • Project generation configuration classes should be specified on the spring.factories file on META-INF folder like below.
io.spring.initializr.generator.project.ProjectGenerationConfiguration=\
com.trendyol.pudoinitializr.application.config.ApiGeneratorsConfiguration, \
com.trendyol.pudoinitializr.acceptance.config.AcceptanceGeneratorsConfiguration
  • So let’s see some examples of custom beans modifying our project generation. Here is an example of custom project contributor bean that injects a Dockerfile to the root of the project
@Component
public class DockerfileProjectContributor implements ProjectContributor {

@Override
public void contribute(Path projectRoot) throws IOException {
FileUtils.copyResources(projectRoot, "classpath:templates/Dockerfile");
}
}
  • This build customization deletes the pseudo dependency entry from pom.xml that is automatically added as mentioned before.
public class ApiBuildCustomizer implements BuildCustomizer<MavenBuild> {
@Override
public void customize(MavenBuild mavenBuild) {
mavenBuild.dependencies().remove("pudo-api-pseudo-dependency");
}
}
  • Following adds spring-boot-web-starter dependency. This customizer bean is added to context conditionally from ApiGeneratorsConfiguration config bean when pudo-api pseudo dependency is selected.
package com.trendyol.pudoinitializr.application.customizations;

@Component
public class DependencyCustomizer implements BuildCustomizer<MavenBuild> {
@Override
public void customize(MavenBuild mavenBuild) {
mavenBuild.dependencies().add("web");
}
}

Testing it with CLI and IDE

  • We can test our custom initializer running at http://localhost:8080 with Intellij IDE. Select File → New → Project and select the Spring Initializr generator. Edit the Server URL as http://localhost:8080 and you can see our customizations in action.
  • On the next page we can see the dependencies we configured as selectable.
  • CLI can be used as an alternative way to use our initalizer. Spring Boot CLI tool is capable of generating project from an initializr. Below is an example command with parameters to generate using Spring Boot CLI.
spring init --target http://localhost:8080 -x -d pudo-api,mapstruct -n pudo-demo-api -a pudo-demo-api
  • curl is an another alternative to generate a project, directly using our initializer’s APIs. Here is an example usage with parameters.
curl -G http://localhost:8080/starter.zip -d dependencies=pudo-api,mapstruct \
-d artifactId=pudo-demo-api -d name=pudo-demo-api -o pudo-demo-api.zip

Future Work

In the future we can add many more enhancements and customization to our initializer.

  • Validation can be added to conform out team’s naming conventions
  • Customize the ui or create another ui from scratch
  • Use a template engine like mustache to add files to our project.
  • Process pom.xml programmatically and automatically add dependencies without needing to define them

Conclusion

While creating a new application, we can lose time doing same things over and over again. A custom Spring Boot Initializr instance implements our common modifications in a single place while creating the project. Before starting a new business implementation our initial costs are reduced and lead time is improved.

--

--