Customizing Spring Initializr Defaults: Building a Chrome Extension for start.spring.io
Spring Initializr (start.spring.io) is a fantastic tool for bootstrapping Spring Boot projects. However, you might find yourself repeatedly changing the same default values every time you create a new project. In this guide, we’ll create a Chrome extension that let’s you save default preferences and apply them with single click
What We’ll Build
Our extension will set your preferred
- Build tool
- Language
- Spring Boot version
- Adjust default project metadata
- Packaging
- Java version
Project Structure
spring-initializr-customizer/
├── manifest.json
├── content.js
├── popup.html
├── popup.js
├── styles.css
├── background.js
├── icon16.png
├── icon48.png
└── icon128.png
Creating the Manifest
Create manifest.json
file in its root directory that lists important information about the structure and behavior of that extension.
Following manifest file contains required permissions, script file names and shortcut keys to invoke the extension.
You can find more details about mainfest file here
{
"manifest_version": 3,
"name": "Spring Initializr Defaults",
"version": "1.0",
"description": "Customize default values on start.spring.io",
"permissions": [
"storage",
"scripting",
"activeTab",
"tabs",
"commands"
],
"icons": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
},
"host_permissions": [
"https://start.spring.io/*"
],
"action": {
"default_popup": "popup.html"
},
"background": {
"service_worker": "background.js",
"type": "module"
},
"commands": {
"apply-settings": {
"suggested_key": {
"default": "Ctrl+Shift+Z",
"mac": "Command+Shift+Z"
},
"description": "Execute code"
}
},
"content_scripts": [
{
"matches": [
"https://start.spring.io/*"
],
"js": [
"content.js"
]
}
]
}
Code language: Java (java)
Creating the Content Script
The content.js file will load on every page the extension has access to.
For our extension this will set the form values.
// Listen for messages from the background script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === "APPLY_SETTINGS" && message.settings) {
try {
setFormValues(message.settings);
sendResponse({ success: true });
} catch (error) {
console.error("Error applying settings:", error);
sendResponse({ success: false, error: error.message });
}
}
// Must return true if response is sent asynchronously
return true;
});
function setFormValues(settings) {
const language = settings.language;
const project = settings.project;
const packaging = settings.packaging;
const javaVersion = settings.javaVersion;
const springBootVersion = settings.springBootVersion;
const controlDivs = document.querySelectorAll(".control");
controlDivs.forEach((div) => {
let label = div.querySelector("label");
if (label && label.textContent.trim() === "Language") {
// Found the correct "Language" label, now find the language anchor
let languageText;
if (language === "java") {
languageText = "Java";
} else if (language === "kotlin") {
languageText = "Kotlin";
} else if (language === "groovy") {
languageText = "Groovy";
}
if (languageText) {
// Find the anchor tag with the correct language text
const languageElements = div.querySelectorAll("a.radio .radio-content");
languageElements.forEach((element) => {
if (element.textContent.trim() === languageText) {
element.parentElement.click(); // Click the parent <a> element
}
});
}
}
if (label && label.textContent.trim() === "Spring Boot") {
const springBootVersionElements = div.querySelectorAll(
"a.radio .radio-content"
);
springBootVersionElements.forEach((element) => {
if (element.textContent.trim() === springBootVersion) {
element.parentElement.click(); // Click the parent <a> element
}
});
}
if (label && label.textContent.trim() === "Project") {
// Found the correct "Project" label, now find the language anchor
let projectText;
if (project === "Gradle - Groovy") {
projectText = "Gradle - Groovy";
} else if (project === "Gradle - Kotlin") {
projectText = "Gradle - Kotlin";
} else if (project === "Maven") {
projectText = "Maven";
}
if (projectText) {
const languageElements = div.querySelectorAll("a.radio .radio-content");
languageElements.forEach((element) => {
if (element.textContent.trim() === projectText) {
element.parentElement.click(); // Click the parent <a> element
}
});
}
}
if (label && label?.textContent.trim() === "Packaging") {
let packagingText;
if (packaging === "Jar") {
packagingText = "Jar";
} else if (packaging === "War") {
packagingText = "War";
}
if (packagingText) {
// Find the anchor tag with the correct language text
const languageElements = div.querySelectorAll("a.radio .radio-content");
languageElements.forEach((element) => {
if (element.textContent.trim() === packagingText) {
element.parentElement.click();
}
});
}
}
if (label && label.textContent.trim() === "Java") {
if (javaVersion) {
// Find the anchor tag with the correct language text
const javaVersionElements = div.querySelectorAll(
"a.radio .radio-content"
);
javaVersionElements.forEach((element) => {
if (element.textContent.trim() === javaVersion) {
element.parentElement.click(); // Click the parent <a> element
}
});
}
}
});
const groupElement = document.getElementById("input-group");
if (groupElement && settings.group.trim() !== "") {
groupElement.value = settings.group;
groupElement.dispatchEvent(new Event("input", { bubbles: true }));
}
// Set the Artifact ID
const artifactElement = document.getElementById("input-artifact");
if (artifactElement && settings.artifact.trim() !== "") {
artifactElement.value = settings.artifact;
artifactElement.dispatchEvent(new Event("input", { bubbles: true }));
}
const nameElement = document.getElementById("input-name");
if (nameElement && settings.artifact.trim() !== "") {
nameElement.value = settings.artifact;
nameElement.dispatchEvent(new Event("input", { bubbles: true }));
}
const packageNameElement = document.getElementById("input-packageName");
if (packageNameElement && settings.package?.trim() !== "") {
packageNameElement.click();
packageNameElement.value = settings.package;
}
}
Code language: Java (java)
Creating the background.js
The background.js file will listen to user actions and sends message to content.js file to perform corresponding work.
chrome.runtime.onConnect.addListener((port) => {
port.onMessage.addListener(async (msg) => {
switch (msg.type) {
case "APPLY_SETTINGS":
try {
const tabs = await chrome.tabs.query({
active: true,
currentWindow: true,
});
const activeTab = tabs[0];
if (!activeTab) {
console.error("No active tab found");
port.postMessage({ type: "ERROR", message: "No active tab found" });
return;
}
chrome.tabs.sendMessage(
activeTab.id,
{
type: "APPLY_SETTINGS",
settings: msg.settings,
},
(response) => {
if (chrome.runtime.lastError) {
console.error("Error:", chrome.runtime.lastError);
port.postMessage({
type: "ERROR",
message: "Failed to apply settings. Please refresh the page.",
});
} else {
port.postMessage({ type: "SETTINGS_APPLIED" });
}
}
);
} catch (error) {
console.error("Error in background script:", error);
port.postMessage({
type: "ERROR",
message: "Error applying settings: " + error.message,
});
}
break;
case "SAVE_SETTINGS":
try {
await chrome.storage.sync.set({ settings: msg.settings });
port.postMessage({ type: "SETTINGS_SAVED" });
} catch (error) {
console.error("Error saving settings:", error);
port.postMessage({
type: "ERROR",
message: "Error saving settings: " + error.message,
});
}
break;
case "GET_SETTINGS":
try {
const data = await chrome.storage.sync.get("settings");
port.postMessage({
type: "SETTINGS_RETRIEVED",
settings: data.settings,
});
} catch (error) {
console.error("Error retrieving settings:", error);
port.postMessage({
type: "ERROR",
message: "Error retrieving settings: " + error.message,
});
}
break;
}
});
});
chrome.commands.onCommand.addListener((command) => {
if (command === "apply-settings") {
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
const data = await chrome.storage.sync.get("settings");
chrome.tabs.sendMessage(tabs[0].id, {
type: "APPLY_SETTINGS",
settings: data.settings,
});
});
}
});
Code language: Java (java)
Creating the Popup Interface
Create popup.html for configuring default values:
<!DOCTYPE html>
<html>
<head>
<title>Spring Initializr Settings</title>
<style>
body {
width: 300px;
padding: 10px;
}
.form-group {
margin-bottom: 10px;
}
label {
display: block;
margin-bottom: 5px;
}
input, select {
width: 100%;
padding: 5px;
}
.button-group {
display: flex;
gap: 10px;
}
button {
flex: 1;
padding: 8px;
background: #6db33f;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button#applyButton {
background: #28a745;
}
.status {
margin-top: 10px;
text-align: center;
color: #6db33f;
opacity: 0;
transition: opacity 0.3s;
}
.status.visible {
opacity: 1;
}
</style>
</head>
<body>
<div class="form-group">
<label for="group">Group :</label>
<input type="text" id="group" name="group"><br>
</div>
<div class="form-group">
<label for="artifact">Artifact :</label>
<input type="text" id="artifact" name="artifact"><br>
</div>
<div class="form-group">
<label for="package">Package :</label>
<input type="text" id="package" name="package"><br>
</div>
<div class="form-group">
<label for="project">Project:</label>
<select id="project" name="language">
<option value="Gradle - Groovy">Gradle - Groovy</option>
<option value="Gradle - Kotlin">Gradle - Kotlin</option>
<option value="Maven">Maven</option>
</select>
</div>
<div class="form-group">
<label for="packaging">Packaging:</label>
<select id="packaging" name="packaging">
<option value="Jar">Jar</option>
<option value="War">War</option>
</select>
</div>
<div class="form-group">
<label for="springBootVersion">Spring Boot Version:</label>
<input type="text" id="springBootVersion" name="springBootVersion">
</div>
<div class="form-group">
<label for="language">Language:</label>
<select id="language" name="language">
<option value="java">Java</option>
<option value="kotlin">Kotlin</option>
<option value="groovy">Groovy</option>
</select>
</div>
<div class="form-group">
<label for="javaVersion">Java Version:</label>
<input type="text" id="javaVersion" name="javaVersion">
</div>
<div class="button-group">
<button type="button" id="saveButton">Save</button>
<button type="button" id="applyButton">Apply</button>
</div>
<div id="status" class="status">Settings saved!</div>
<script src="popup.js"></script>
</body>
</html>
Code language: Java (java)
Adding Popup Logic
Create popup.js to handle settings:
let port;
function getSettings() {
return {
group: document.getElementById("group").value,
artifact: document.getElementById("artifact").value,
package: document.getElementById("package").value,
springBootVersion: document.getElementById("springBootVersion").value,
javaVersion: document.getElementById("javaVersion").value,
language: document.getElementById("language").value,
project: document.getElementById("project").value,
packaging: document.getElementById("packaging").value,
};
}
document.addEventListener("DOMContentLoaded", async () => {
// Connect to the background service worker
port = chrome.runtime.connect({ name: "popup" });
// Request current settings
port.postMessage({ type: "GET_SETTINGS" });
// Listen for messages from the background script
port.onMessage.addListener((msg) => {
switch (msg.type) {
case "SETTINGS_RETRIEVED":
if (msg.settings) {
document.getElementById("group").value = msg.settings.group;
document.getElementById("artifact").value = msg.settings.artifact;
document.getElementById("package").value = msg.settings.package;
document.getElementById("springBootVersion").value =
msg.settings.springBootVersion;
document.getElementById("javaVersion").value =
msg.settings.javaVersion;
document.getElementById("language").value = msg.settings.language;
document.getElementById("project").value = msg.settings.project;
document.getElementById("packaging").value = msg.settings.packaging;
}
break;
case "SETTINGS_SAVED":
showStatus("Settings saved!");
break;
}
});
});
// Save button only saves to storage
document.getElementById("saveButton").addEventListener("click", () => {
const settings = getSettings();
port.postMessage({
type: "SAVE_SETTINGS",
settings: settings,
});
});
// Apply button sends settings to content script via background script
document.getElementById("applyButton").addEventListener("click", () => {
const settings = getSettings();
port.postMessage({
type: "APPLY_SETTINGS",
settings: settings,
});
showStatus("Settings applied!");
});
function showStatus(message) {
const status = document.getElementById("status");
status.textContent = message;
status.classList.add("visible");
setTimeout(() => {
status.classList.remove("visible");
}, 2000);
}
let port;
function getSettings() {
return {
group: document.getElementById("group").value,
artifact: document.getElementById("artifact").value,
package: document.getElementById("package").value,
springBootVersion: document.getElementById("springBootVersion").value,
javaVersion: document.getElementById("javaVersion").value,
language: document.getElementById("language").value,
project: document.getElementById("project").value,
packaging: document.getElementById("packaging").value,
};
}
document.addEventListener("DOMContentLoaded", async () => {
// Connect to the background service worker
port = chrome.runtime.connect({ name: "popup" });
// Request current settings
port.postMessage({ type: "GET_SETTINGS" });
// Listen for messages from the background script
port.onMessage.addListener((msg) => {
switch (msg.type) {
case "SETTINGS_RETRIEVED":
if (msg.settings) {
document.getElementById("group").value = msg.settings.group;
document.getElementById("artifact").value = msg.settings.artifact;
document.getElementById("package").value = msg.settings.package;
document.getElementById("springBootVersion").value =
msg.settings.springBootVersion;
document.getElementById("javaVersion").value =
msg.settings.javaVersion;
document.getElementById("language").value = msg.settings.language;
document.getElementById("project").value = msg.settings.project;
document.getElementById("packaging").value = msg.settings.packaging;
}
break;
case "SETTINGS_SAVED":
showStatus("Settings saved!");
break;
}
});
});
// Save button only saves to storage
document.getElementById("saveButton").addEventListener("click", () => {
const settings = getSettings();
port.postMessage({
type: "SAVE_SETTINGS",
settings: settings,
});
});
// Apply button sends settings to content script via background script
document.getElementById("applyButton").addEventListener("click", () => {
const settings = getSettings();
port.postMessage({
type: "APPLY_SETTINGS",
settings: settings,
});
showStatus("Settings applied!");
});
function showStatus(message) {
const status = document.getElementById("status");
status.textContent = message;
status.classList.add("visible");
setTimeout(() => {
status.classList.remove("visible");
}, 2000);
}
Code language: Java (java)
Installation
From Microsoft Edge store
If you are using the Microsoft Edge browser
- Visit the Microsoft Edge Store
- Search for “Spring Initializr Defaults” extension or you can click on here to directly access the extension page
- Click on “Get” button to install
Manual Installation
Method 1
- Download the spring-initializer-defaults extension zip file from here
- Drap and Drop zip file on to Chrome/Edge/Brave browser to install the extension
Method 2
- Download the spring-initializer-defaults extension zip file from here
- Unzip the contents to a folder
- Open Chrome and go to
chrome://extensions/
- Enable “Developer mode”
- Click “Load unpacked” and select your extension directory
Usage
- Visit start.spring.io
- Click the extension icon to open the popup form
- Fill your defaults and click on “Save” button to save the options.
- Click on the “Apply” button to apply the settings
- You can also use Ctrl+Alt+Shift Z shortcut key to apply the stored default values
Troubleshooting
Conclusion
This Chrome extension streamlines your Spring Boot project creation workflow by automatically setting your preferred defaults on start.spring.io. You can easily extend it to handle additional customizations by modifying the content script and popup interface.
You can download the source code for the extension from GitHub