Skip to content

Add support for source set and Spring profile specific application properties#1807

Open
denisfalqueto wants to merge 4 commits into
spring-io:mainfrom
denisfalqueto:main
Open

Add support for source set and Spring profile specific application properties#1807
denisfalqueto wants to merge 4 commits into
spring-io:mainfrom
denisfalqueto:main

Conversation

@denisfalqueto

@denisfalqueto denisfalqueto commented Jul 2, 2026

Copy link
Copy Markdown

Add support for source set and Spring profile specific application properties

Disclaimer

These commits were made with aid of Claude Code's models. But they were supervised by me, in each step. I assume responsibility for the reasoning and approval of these changes, as well as providing addition information.

Motivation

The ApplicationProperties abstraction introduced in initializr-generator-spring allows contributors to add properties to the generated project's configuration file. However, it is currently a single flat key-value container, and both ApplicationPropertiesContributor and ApplicationYamlPropertiesContributor write it to a hardcoded location: src/main/resources/application.properties (or .yaml).

This means an ApplicationPropertiesCustomizer has no way to express two very common needs of generated projects:

  1. Test-only configuration. Properties that should only apply when running tests (for example, an in-memory datasource URL, or disabling a cloud integration during tests) belong in src/test/resources/application.properties, not in the main configuration packaged with the application.

  2. Profile-specific configuration. Spring Boot's convention of application-{profile}.properties / application-{profile}.yaml files is the idiomatic way to separate configuration per environment, yet the generation model cannot produce these files today.

As a result, custom Initializr instances that need either behavior must bypass the ApplicationProperties abstraction entirely and write raw files through a dedicated ProjectContributor, duplicating the properties/YAML rendering logic that already exists in this module.

This PR generalizes the model so that application properties can be targeted at a (source set, Spring profile) pair, while keeping the existing single-file behavior and public API fully intact.

What this change does

ApplicationProperties becomes a composite of itself. The root instance — the bean that customizers already receive — represents the main source set with the default profile, exactly as before. A new section(SourceSet, String profile) method returns the ApplicationProperties for any other (source set, profile) combination, creating it on demand:

// unchanged: main source set, default profile
properties.add("spring.application.name", "demo");

// test source set, default profile -> src/test/resources/application.properties
properties.section(SourceSet.TEST)
        .add("spring.datasource.url", "jdbc:h2:mem:test");

// main source set, 'dev' profile -> src/main/resources/application-dev.properties
properties.section(SourceSet.MAIN, "dev")
        .add("logging.level.root", "DEBUG");

// test source set, 'integration' profile -> src/test/resources/application-integration.properties
properties.section(SourceSet.TEST, "integration")
        .add("spring.application.name", "it");

The two file contributors now share a new base class, AbstractApplicationPropertiesContributor, which iterates the root and its sections and resolves the output path following the standard convention:

src/{sourceSet}/resources/application[-{profile}].{properties|yaml}

The file for the main source set and default profile is always written, even when empty, preserving today's behavior. Files for other sections are only written when the section actually contains properties.

The reference documentation now covers the ApplicationPropertiesCustomizer extension point, including how to target sections, with a runnable example in ProjectCustomizationExamples.

Design decisions

No breaking change to the public API. The signatures of ApplicationProperties (all add/get/contains/remove overloads), ApplicationPropertiesCustomizer, and both contributors' constructors are untouched. Existing customizers — including the lambda-based registrations that are the documented idiom for this extension point — keep working unmodified and keep targeting the main/default file. The feature is purely additive: one new public method (section, plus a convenience overload) and one new public enum (SourceSet).

Dimensions are fixed by navigation, not by method parameters. The (source set, profile) pair is fixed once when obtaining the section, and the section then exposes the exact same API as the root, keeping every typed add overload untouched. This mirrors the idiom already established in this code base by MavenBuild, where build.profiles().id("profile-id") navigates to a MavenProfile that exposes the regular build-configuration API scoped to that profile.

Default profile is represented by null, not the string "default". The literal "default" has its own semantics in Spring (spring.profiles.default) and using it as a sentinel could suggest a application-default.properties file should be generated. A null profile unambiguously means "the profile-less application.{properties|yaml} file".

Sections cannot be nested. Calling section(...) on a section throws an IllegalStateException, and section(SourceSet.MAIN, null) on the root returns the root itself, so there is exactly one canonical instance per (source set, profile) pair.

Testing

Unit tests were added for the section semantics (root identity for main/default, idempotent section retrieval, isolation between sections, rejection of nesting and of blank profile names) and for the contributor output paths (test source set, profile-specific files, combination of both, empty sections producing no file, and the main/default file still always being written).

ApplicationPropertiesComplianceTests was extended with the source set/profile pair as an additional parameterized axis: for both the properties and YAML formats, a customizer targeting a section is verified end-to-end to produce the expected file at the expected location, while the main/default file keeps being generated.

All existing tests pass unchanged, which follows from the API being untouched.

Why SourceSet lives in io.spring.initializr.generator.buildsystem

Although this PR only consumes SourceSet from the properties support in initializr-generator-spring, the enum was deliberately placed in the io.spring.initializr.generator.buildsystem package of initializr-generator, for three reasons.

First, the concept genuinely belongs to the build-system domain, not to the properties feature. The main/test split is defined by the build systems this project abstracts over: it is Maven's Standard Directory Layout (src/main, src/test, mirrored lifecycle phases and output directories) and it is literally Gradle's source set concept, from which the name is borrowed. A type that answers "which tree of the standard directory layout does this artifact belong to" is build-system vocabulary regardless of which feature happens to ask the question.

Second, the package already contains the direct sibling of this concept: DependencyScope. That enum models the build-system-agnostic classification of dependencies (compile, runtime, test-compile, ...) in a neutral form that each BuildSystem implementation translates to its own representation. SourceSet does exactly the same for the classification of sources and resources. Placing the two classification axes side by side keeps the build abstraction's vocabulary coherent and discoverable.

Third, it enables reuse without churn. Other contributors in the code base hardcode the same layout knowledge today — for example WebFoldersContributor resolves src/main/resources/templates and src/main/resources/static as string literals — and language source-code contributors are inherently organized around the main/test split. Hosting the enum in the shared buildsystem package lets those spots adopt it incrementally as further consumers appear.

No module dependencies change: initializr-generator-spring already depends on initializr-generator.

Closes #1806

denisfalqueto and others added 3 commits July 2, 2026 13:34
Add a SourceSet enum to the buildsystem package, modeling the main/test
split of the standard directory layout shared by the supported build
systems, alongside the existing DependencyScope classification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Denis A. Altoé Falqueto <denisfalqueto@gmail.com>
ApplicationProperties becomes a composite: the root instance keeps
representing the main source set with the default profile, and a new
section(SourceSet, String) method returns the properties of any other
(source set, profile) pair, creating it on demand. Sections expose the
exact same API as the root and cannot be nested.

The properties and YAML contributors now share a new base class,
AbstractApplicationPropertiesContributor, which writes one file per
non-empty section following the
src/{sourceSet}/resources/application[-{profile}] convention. The file
for the main source set and default profile is still always written,
preserving the existing behavior, and the public API is untouched.

Compliance tests gain the (source set, profile) pair as an additional
parameterized axis for both configuration file formats.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Denis A. Altoé Falqueto <denisfalqueto@gmail.com>
Describe in the configuration guide how application properties can be
contributed to the generated project, including how to target another
source set or Spring profile through sections, with a runnable example.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Denis A. Altoé Falqueto <denisfalqueto@gmail.com>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jul 2, 2026
@denisfalqueto denisfalqueto marked this pull request as draft July 2, 2026 17:21
Declare the local variable as @nullable Object[] to match the
JSpecify-annotated return type of Arguments.get() in recent JUnit
versions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Denis A. Altoé Falqueto <denisfalqueto@gmail.com>
@denisfalqueto denisfalqueto marked this pull request as ready for review July 2, 2026 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Provide mechanism to customize the destination of ApplicationProperties

2 participants