CodeGym /Courses /Spring Boot /spring.config.import: breaking up the config

spring.config.import: breaking up the config

Spring Boot
Level 16, Lesson 0
Available

1. When application.yaml grows out of control

When a project is small, application.yaml looks neat and tidy: the app name, the port, a couple of flags — beautiful. But then a week or two passes, you add profiles, “one more tiny parameter,” and then a large data block (for example, a list of courses). And now the file is no longer a config file, but a “journal of observations about application life,” where everything is mixed together.

The problem is not YAML as a format. The problem is that YAML makes it very easy to “add just a little more,” and you do not notice how you lose structure. In one document, basic settings, profile-specific differences, accidental overrides, and huge data blocks all start living together. At some point, any new student opens application.yaml and feels exactly the same thing as when looking at someone else’s build.gradle.kts: “I didn’t touch anything; it somehow became scary by itself.”

To feel this, a tiny fragment is enough (and yes, in a real project this quickly grows into dozens of screens):

spring:
  application:
    name: catalog-service # basic “frame”: who we are

app:
  catalog:
    title: "Spring+ Catalog" # small “human-friendly” settings
    courses: # there will be a huge list of data below, and it will start inflating the file
      - id: "spring-boot"
        title: "Spring Boot"

From a maintenance perspective, this is unpleasant for two reasons. First, the file loses “one idea”: it is no longer about the basic configuration, not about the environment, not about the data — it is about everything at once. Second, it becomes hard to explain where a specific value came from: it might be somewhere near the bottom of this giant file, it might be in a profile, it might have been overridden by an external file. We want to restore a simple architecture for the config: “here is the basic frame,” “here are the profile-specific differences,” “here is the catalog data.”

2. How spring.config.import works

spring.config.import is a Spring Boot mechanism that lets you attach additional configuration documents to the application’s overall set of properties. Important: this is not an “include inside YAML” (as in some other systems), but rather a Boot-level mechanism that works during the assembly of the final Environment configuration.

Think of it as “connecting another layer of settings.” Boot reads the main configuration document, notices spring.config.import in it, then loads the specified resources and adds them to the overall picture. These values become ordinary application properties: they take part in priority resolution, can override each other, and for the code it all looks like one common set of settings.

It is convenient to imagine configuration loading as a short pipeline:

flowchart TD
    A["Boot found application.yaml"] --> B["Read properties from the file"]
    B --> C["Saw spring.config.import"]
    C --> D["Loaded imported files"]
    D --> E["Assembled the final Environment (property set)"]
    E --> F["The application starts with a ready-made configuration"]

spring.config.import also has another nice “everyday” effect: it enforces discipline. Once you have moved data into a separate file, you can explain in words what this file is and why it exists. And if a file cannot be explained in words, it is almost always unnecessary or badly named.

In practice, an import can be specified as a single line or as a list. In YAML, a list is usually easier to read, especially when there is more than one import:

spring:
  config:
    import:
      # 1) regular import from resources (classpath)
      - "classpath:catalog-data.yaml"
      # 2) optional: if the file is missing, startup will not fail
      - "optional:file:./config/catalog-extra.yaml"

For now, remember the main idea: spring.config.import answers the question “what else should be connected to the configuration” once the main config has already been found and is being read. This is a different question from controlling configuration search locations: different properties and a different startup stage are used there.

3. Connecting catalog-data.yaml via import

A very common beginner mistake (and not only a beginner one) is: “I put catalog-data.yaml in src/main/resources, so Spring Boot must read it.” Alas, no. Boot is not a psychic and not a filename fortune teller. It has a standard contract for which configuration files it searches automatically. If your file does not match that contract, it will just be an ordinary resource on the classpath: sitting there, minding its own business.

And honestly, that is good. Otherwise, configuration anarchy would start in the project: any file in resources could suddenly affect application behavior. You would delete “some old yaml,” and the service would suddenly start listening on a different port — fun, but not for long.

So catalog-data.yaml is a file with an arbitrary name that Boot will not connect automatically. To make it part of the configuration, you must explicitly say: “this document is configuration too.” And that is exactly what spring.config.import does.

The simplest option for our project looks like this: in application.yaml, we connect catalog-data.yaml from the classpath.

spring:
  application:
    name: catalog-service
  config:
    # explicitly connect an arbitrary YAML as part of the configuration
    import: "classpath:catalog-data.yaml"

app:
  catalog:
    title: "Spring+ Catalog" # the “frame” and short settings are here

After that, the keys from catalog-data.yaml stop being “just text in resources” and become a full part of the Environment. In other words, any app.catalog.* described in catalog-data.yaml will be treated as a regular application property.

4. Priorities and overrides with import

When people first see spring.config.import, they often expect behavior like “as if I just placed another file underneath,” meaning that the main application.yaml is always primary and the imported one is secondary. In Spring Boot, reality is trickier and more useful: import is not just “add another file,” it is “embed it into the overall configuration,” and when values conflict, it is important to know who wins.

For this course, we use a simple rule: values from the imported file have higher priority than values in the file that declared the import. That means the imported file can override what was set in the source document. This is a convenient mechanism, but also a source of surprises if you do not remember where things are defined.

Let us look at a tiny conflict. Suppose application.yaml defines a title, and the imported file defines it differently:

# application.yaml
app:
  catalog:
    title: "Base title" # base value (may be overridden)

spring:
  config:
    import: "classpath:catalog-data.yaml" # import is “higher” in priority within this pair
# catalog-data.yaml
app:
  catalog:
    title: "Imported title" # this value wins in a conflict

The final value of app.catalog.title will be "Imported title". Yes, the imported file wins. That means import is a powerful tool, and it should not be treated as “just place two yaml files next to each other.” If you want the base settings to remain base, agree with yourself (and the team) that the imported file contains, for example, data (courses), while the base file contains the frame (title, behavior flags, limits).

It is useful to keep a conflict table in mind:

Where the property is defined Example What wins in a conflict
In the source file (application.yaml)
app.catalog.title=Base
Loses if the same property exists in the import
In the imported file (catalog-data.yaml)
app.catalog.title=Imported
Wins within the “source ↔ import” pair

One more small but pleasant nuance: the same import is not pulled in endlessly. If you import the same location twice, Boot does not “amplify” its influence; it simply treats it as duplication. In real life, this saves you from accidental cycles and from the feeling of “I imported it twice, so now it must definitely work.” No, it does not. Better once, but intentionally.

5. Import locations: classpath: and file:

As soon as you say “import the file,” a practical question immediately arises: how exactly do I specify the path? And here beginners often get the false intuition “well, it is the path from the project root.” No. The path in spring.config.import is a resource location, and it has its own rules. In general, there are two classes of locations: fixed and relative.

A fixed location means you explicitly state where to take the resource from: from the classpath (classpath:) or from the file system (file:). Such a path always means the same thing, regardless of where application.yaml is located.

A relative location means you write a path without an explicit prefix (classpath:/file:) and without an absolute beginning (/). In this case, Boot treats the path as relative to the file that declared the import. And here is the trap: “relative to the file” does not mean “relative to the project root.” It means “relative to the place where the config document lives.”

Let us compare the options with examples (and at the same time avoid pretending this is obvious on the first try):

Import example Type How to read it in plain English
classpath:catalog-data.yaml
fixed “Take the catalog-data.yaml file from the application resources”
file:./config/catalog-extra.yaml
fixed “Take the file next to the project, in the ./config/ folder”
optional:file:./config/catalog-extra.yaml
fixed + optional “If the file exists, connect it; if not, do not fail”
catalog/catalog-data.yaml
relative “Look for the file relative to the YAML where the import is written”

Relative paths are especially useful when you want to keep configs next to each other in one folder and avoid repeating classpath:. But they are also the most common reason for “why is this not working, I wrote the path correctly.” Remember a simple check: if you are not sure what the relative path is measured from, temporarily use a fixed classpath:. It is a bit more verbose, but it almost always clears things up at the start.

6. optional: in import

Sometimes an external file is needed only sometimes. For example, you want to keep a local override next to the project; it exists only on one machine and should not break startup for everyone else. For this, the location has the optional: prefix.

spring:
  config:
    import: "optional:file:./config/catalog-extra.yaml"

For now, we only need one fact here: import can be mandatory or optional. If the file is found, Boot reads it as a regular configuration layer. If the file is missing, that layer is simply skipped. Where optional is truly appropriate and where it is better to fail startup honestly is a question of startup policy, not of the import mechanism itself.

7. Refactoring: moving data into catalog-data.yaml

Now let us put everything together in our training context, catalog-service. The idea is simple: the base application.yaml should remain compact and be responsible for the “frame of the application,” while the large list of courses (or another large data block) is moved into a separate catalog-data.yaml. Then the configuration starts to look like a neat folder instead of one endless file where everything is stored “by some sort of random principle.”

From the point of view of resource structure, it looks roughly like this (without unnecessary magic):

src/
└── main/
    └── resources/
        ├── application.yaml
        ├── application-local.yaml
        ├── application-dev.yaml
        ├── application-prod.yaml
        └── catalog-data.yaml

In application.yaml, we keep the common settings and the explicit connection of the data file:

spring:
  application:
    name: catalog-service # the service's common identity
  config:
    import: "classpath:catalog-data.yaml" # connect the large data block separately

app:
  catalog:
    title: "Spring+ Catalog" # the “frame”
    max-featured-count: 4 # a small behavior parameter

And in catalog-data.yaml, we keep the large data block. In reality it will be longer, but the principle is the same:

app:
  catalog:
    courses: # data (usually the “heaviest” part)
      - id: "spring-boot"
        slug: "spring-boot"
        title: "Spring Boot"
      - id: "spring-core"
        slug: "spring-core"
        title: "Spring Core"

Notice an important detail: both files continue to use the same prefix app.catalog.*. This is not a “different subsystem,” not a “different namespace,” and not a “special data format.” It is one configuration layer, just physically split across several documents.

If you want to quickly verify that the import really works, you can temporarily make a small debug runner that prints some value from the course list. This is not an architectural solution, but a “flashlight” — you shine it, check, and put it away. For example, through Environment:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

@Configuration
class DebugConfig {

  @Bean
  org.springframework.boot.ApplicationRunner printFirstCourse(Environment env) {
    // Check the final Environment: if import did not work, this is often null
    return args -> System.out.println(
        env.getProperty("app.catalog.courses[0].slug") // expected: spring-boot
    );
  }
}

If the import was not connected, you will most likely see null. If it was connected, you will see the expected slug of the first course. And yes, it is normal to verify configuration this way: “we look at the final value, not guess from the files.”

8. Common mistakes when working with spring.config.import

The first few times, spring.config.import causes the same stumbling blocks for almost everyone, because the brain tries to force an old habit: “well, the file is in resources, so it should work.” Here it is important not to blame yourself, but calmly remember a few rules: where Boot looks for config automatically, what counts as an import, and why priorities can be surprising. The mistakes below come straight from this category.

Mistake #1: splitting the config into one file per key.
Sometimes you want to create title.yaml, limits.yaml, flags.yaml, courses.yaml — and in the end you do not get order, but a new quest: “which of the 12 files contains the setting I am looking for?” Decomposition should be semantic. For our course, it makes sense to separate large concerns: the base app config, environment-specific profile differences, and a large data block (for example, the course list). If a file does not answer the question “what is its role?”, it is unnecessary.

Mistake #2: expecting catalog-data.yaml to be connected automatically.
By default, Boot does not read arbitrary YAML files just because they are nearby. It reads standard application*.yaml, and everything else only by your explicit command. So “I put the file in resources” is not yet configuration; it is just a text resource. To make it a config, you need spring.config.import.

Mistake #3: mixing up the priority and wondering “why is the value different?”
If you define the same key in both application.yaml and the imported file, the import wins. That is convenient, but surprising if you expected the opposite. The practical fix is simple: try not to duplicate keys without a real reason. And if duplication is needed (for example, for a deliberate override), do it intentionally and document it at least for yourself: “there is an override here, and it is supposed to win.”

Mistake #4: confusing a relative path with a path from the project root.
import: "catalog/catalog-data.yaml" does not mean “the catalog folder at the repository root.” It is a path that is calculated relative to the file where the import is written. Today it may match your expectations, and tomorrow you slightly change the config layout — and the relative path suddenly starts pointing “somewhere else.” When in doubt, use a fixed classpath: or file: — it is easier to diagnose.

Mistake #5: repeating the same import in the hope that “this time it will definitely work.”
Boot imports the same location only once. Repetition does not “strengthen” the configuration; it adds noise and makes the file harder to read. If the import does not work, the problem is usually the location (the file was not found) or the expectation (you imported not what you wanted). Repeating the import is not a cure, but rather a sedative — without the effect.

1
Task
Spring Boot, level 16, lesson 0
Locked
Importing a Message from a Nonstandard YAML File
Importing a Message from a Nonstandard YAML File
1
Task
Spring Boot, level 16, lesson 0
Locked
Override value via import
Override value via import
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION