1. The role of DataSource configuration
DataSource already exists as a real bean, but this is where the hard truth begins: it is not telepathic. It needs to know where the database lives, how to reach it, and which credentials to use. Without that, the application may be written perfectly, but it will resemble a car with no gasoline: the steering wheel turns, the headlights blink, but there is nowhere to go.
The underlying infrastructure chain is already clear: driver, DataSource, JPA layer. Now it needs specific address, port, and credentials, otherwise the whole scheme will remain beautiful, but useless.
In Spring Boot, configuration is not "I'll just fix the connection string somewhere in the code", but a separate discipline. We want the project shop-data-jpa to run the same way for all students and at the same time not turn into a pile of hardcoded values that you are embarrassed to show yourself a month later.
The practical meaning of DataSource
DataSource is an object that provides connections to the DB (and usually does this through a connection pool, but we will examine the pool in detail in the next lecture). Right now, the main thing is simple: for DataSource to create a connection, it needs parameters.
In Spring Boot, these parameters are usually set in configuration, through properties like:
- spring.datasource.url
- spring.datasource.username
- spring.datasource.password
These are the properties we will learn to set the "grown-up" way.
2. Configuration files: application.yml and application-dev.yml
When a project is just starting, it is very tempting to say: "well, I will just put everything into application.yml for now and figure it out later." It is a bit like putting all your stuff into one box labeled "I will sort it out later." Usually that "later" never comes, and the box turns into the legendary "Misc" folder, where half of your life and a random receipt from a store in 2019 live.
In Spring Boot, it is customary to split configuration into a general layer and an environment-specific layer. The general layer is what almost never changes: the application name, common settings. The environment-specific layer is what depends on the environment (dev, test, prod): DB address, password, sometimes logging. For a training project, this is especially important: we want a "simple dev start", but we do not want to lock in bad habits.
The minimal application.yml
In the root of the project resources (src/main/resources), we will create application.yml, which will contain only general settings. At the start, the application name will be enough, and in training mode, enabling the dev profile.
spring:
application:
name: shop-data-jpa # Application name (visible, for example, in logs)
profiles:
active: dev # Training default: start in the dev profile
Yes, in real projects spring.profiles.active is often not committed to the repository, but set via environment variables or launch parameters. But for a training project, this is an honest compromise: less "launch magic", more understanding of what is actually happening. A little below, we will still look at how to enable a profile through the environment.
Profile file application-dev.yml
Now we will create application-dev.yml and move there everything tied to a specific environment, including connection parameters for PostgreSQL.
spring:
datasource:
url: jdbc:postgresql://localhost:5432/shop # DB address (host:port/database)
username: shop # Login for connection
password: shop # Password for connection
This is the most straightforward option. It works. But it has one downside: the parameters are already hardcoded. For a training setup, that is acceptable, but we want to learn how to do the same thing through environment variables, so that we can easily change the host, port, and DB name without editing YAML and without making "committing a password" a habit.
3. JDBC URL for PostgreSQL: what it consists of
At first, a JDBC URL looks like a spell: make a mistake in one character, and the demon Connection refused is invoked. But if you break the string into parts, things become much calmer. In this lecture, we are not trying to become DBAs and we are not diving into network details; it is enough for us to learn how to read the URL with our eyes and understand exactly what is being specified there.
For PostgreSQL, the basic form looks like this: jdbc:postgresql://host:port/database. And this is the key: host, port, database. If you understand these three things, you can diagnose 80% of connection problems without reading tea leaves in the logs.
Breaking the URL into pieces
Here is the "classic" URL for our course:
jdbc:postgresql://localhost:5432/shop
Let us break the string into parts so that the brain does not pretend that "this is just one incomprehensible string":
| Part | Example | Meaning |
|---|---|---|
| jdbc:postgresql | jdbc:postgresql | JDBC protocol + PostgreSQL driver |
| host | localhost | Where the database lives (from the application's point of view) |
| port | 5432 | PostgreSQL port (default is 5432) |
| database | shop | The name of the database inside PostgreSQL |
An important nuance about localhost and Docker
Right now, the application is running on your machine, and PostgreSQL is in a container, but with port 5432:5432 exposed. For the application, this simply means: the database is available at localhost:5432. If you ever run the application in Docker too, localhost becomes a trap, because inside a container localhost means the container itself. But today that is not our task. For now, it is enough to remember the rule: the host depends on where you are connecting from.
4. Login and password: keeping them out of code
Login and password are mandatory parts of the connection, without which PostgreSQL quite reasonably asks: "Who are you?" At the same time, beginner developers sometimes try to "simplify" their lives and write the password directly in code, in the style of new DriverManager.getConnection("...", "user", "password"). Convenient? Yes. Good? No. And not because "the textbook says so", but because it breaks reproducibility and security.
In our training project, the password is simple (shop), and nobody is hacking your laptop for the sake of a training database. But the habit of putting the password in code migrates very easily into real projects, and then the fun begins: the password ends up in Git, in logs, in screenshots, in commit history, and in karma.
What we do instead
We keep the login and password in configuration. Even better, in environment variables, so that YAML does not turn into a "secret diary".
In Spring Boot, parameters can be set like this:
spring:
datasource:
username: ${DB_USER:shop} # Take from the DB_USER environment variable, otherwise use shop
password: ${DB_PASSWORD:shop} # Take from the DB_PASSWORD environment variable, otherwise use shop
Here ${...:...} is a placeholder: "take the value from the environment, and if it does not exist, use the default."
5. Environment variables and placeholders
Environment variables often sound like "something from DevOps", but in fact they are a basic developer tool. The idea is simple: the code is the same, but values — host, port, password — can be changed from the outside, without editing files and without risking accidentally committing the "production password".
In a training project, they are especially useful because someone already has PostgreSQL using 5432, someone runs several course projects at the same time, and someone just likes suffering (no judgment). We want the project to be adaptable to such conditions with minimal effort.
Configuring application-dev.yml through the environment
Here is a good "training-production" version of application-dev.yml:
spring:
datasource:
# Build the URL from parts so it is easy to override by environment
url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:shop}
username: ${DB_USER:shop} # Login from the environment or default
password: ${DB_PASSWORD:shop} # Password from the environment or default
Pay attention to an important detail: we are not just using environment variables, but setting explicit default values. This makes the configuration readable. When a student opens the file, they immediately see: "Okay, by default this is localhost, 5432, shop."
Setting environment variables (Linux/macOS)
If you are in a terminal on a Unix-like system, it looks like this:
export DB_HOST=localhost # Host where the DB is available for the application
export DB_PORT=5432 # PostgreSQL port
export DB_NAME=shop # Database name
export DB_USER=shop # Login
export DB_PASSWORD=shop # Password
You can do this in the terminal before launching the application from that same terminal.
Setting environment variables (Windows PowerShell)
In PowerShell, this is usually done like this:
$env:DB_HOST="localhost" # Host where the DB is available for the application
$env:DB_PORT="5432" # PostgreSQL port (as a string — that is normal for env)
$env:DB_NAME="shop" # Database name
$env:DB_USER="shop" # Login
$env:DB_PASSWORD="shop" # Password
Yes, the syntax is different. Windows generally likes to be "special", but we still appreciate it.
6. Spring Boot profiles
A profile in Spring is a way to tell the application: "you are running in this mode now, use these settings." And yes, the idea is really simple. The difficulties begin when everything is thrown into one file, and then people wonder why tests suddenly hit the dev database, after which you accidentally delete tables and the day becomes a little more eventful.
In the project, we will gradually live in at least two modes: dev and test. Right now, we need dev: a local PostgreSQL in Docker. test matters here as a reminder that one config for everything almost always leads to confusion and accidental trips to the wrong database.
Including the profile file
If the dev profile is active, Spring Boot automatically picks up application-dev.yml and "layers" it on top of application.yml. That is, a general file + a profile file = the final settings.
To simplify a lot, and that is exactly how we use it today, the logic is this:
application.yml (general settings)
+
application-dev.yml (overrides for dev)
=
final configuration
Setting the active profile
The most direct training way, which we already showed above, is to write in application.yml:
spring:
profiles:
active: dev
But there is also a more flexible option — an environment variable:
export SPRING_PROFILES_ACTIVE=dev # Choose the profile without editing YAML
If you set SPRING_PROFILES_ACTIVE, you usually no longer need to hardcode spring.profiles.active in YAML. This is often how real teams do it: the profile is chosen "from the outside."
Configuration source priorities
In Spring Boot, configuration can come from different sources: YAML files, environment variables, command-line arguments, JVM system properties. The full priority table is a topic for a separate conversation, and it can easily turn into a 10-screen reference guide. But for the course, we need a minimal engineering model so you do not get lost while debugging.
The idea is this: the closer a setting is to the launch — command line, environment — the higher its priority. Configuration files are the base layer.
Simplified priority scheme
| Source | Example | Usual priority |
|---|---|---|
| Launch arguments | --spring.datasource.url=... | Very high |
| Environment variables | SPRING_DATASOURCE_URL=... | High |
| application-dev.yml | profile file | Medium |
| application.yml | general file | Base |
This is a simplification, but it already helps answer the question: "Why is the application connecting somewhere other than what I wrote in YAML?" The common answer: "Because you already have a variable set in the environment that overrides the file."
Checking the applied settings
One of the most dangerous traps in configuration is thinking that "if I wrote it in YAML, then it definitely took effect." Configuration likes surprises: the active profile is wrong, the environment variable overrode the value, the IDE started with different parameters. So it is useful to know how to do a small "reality check."
We are not doing a full smoke test of the connection today — that will be in the practical lecture of the day — but we can do a mini-check: print the active profiles and the URL that Spring sees.
Mini-check via Environment
It is convenient to check this once right after the application starts, but you should not turn such printing into permanent code in the entry point. The purpose is purely diagnostic: see the final values after applying profiles and overrides from the environment, and then remove this temporary code.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;
@SpringBootApplication
public class ShopDataJpaApplication {
public static void main(String[] args) {
// Start the Spring context (after that we can read the applied configuration)
var context = SpringApplication.run(ShopDataJpaApplication.class, args);
// Get Environment from the context to inspect the final property values
Environment env = context.getBean(Environment.class);
// Quick diagnostics: which profile is actually active
System.out.println("Active profiles: " + String.join(", ", env.getActiveProfiles())); // Active profiles: dev
// Quick diagnostics: which URL Spring actually saw (taking profiles and env into account)
System.out.println("DB url: " + env.getProperty("spring.datasource.url")); // DB url: jdbc:postgresql://localhost:5432/shop
}
}
There are two important points here. First, you immediately see the active profiles and do not have to guess. Second, you see the final URL value already taking profiles and environment variables into account. This is very down-to-earth diagnostics that saves time.
Check the active profile and the final URL — then you can safely remove this code, without dragging it into the application's permanent skeleton.
7. Typical mistakes when configuring DataSource
Mistake #1: hardcoding in Java "just to make it work faster".
A beginner sometimes adds the connection string directly in code because it is "faster to test." The problem is not that this is impossible at all, but that you are breaking the very idea of Spring Boot: configuration should be external, manageable, and reproducible. Today you hardcoded localhost, and tomorrow you run the application in a different environment and forget why it is stubbornly talking to the old database.
Mistake #2: mixing general and environment-specific configs in one file without a system.
If you keep both the application name and the dev database parameters, and some "temporary" fixes, in application.yml, then after a couple of days that file turns into a swamp. Profile files (application-dev.yml) are needed not for beauty, but so that environment changes do not turn into a "find 10 differences" game between branches and commits.
Mistake #3: incorrect JDBC URL (most often the port and database name).
A classic case is mixing up host:port, specifying a non-existent database, or forgetting that PostgreSQL listens on 5432. A good habit is to say the URL out loud every time: "connecting to localhost, port 5432, database shop." If you cannot say it, then you are probably not sure yourself what you wrote.
Mistake #4: the wrong profile is enabled, and the database gets blamed.
A very common story: a student edits application-dev.yml, but the application starts without the dev profile. As a result, the settings are not picked up, and the reasoning becomes "PostgreSQL is broken." So the first thing to check when there are problems is the active profiles. That is almost always faster than reinstalling Docker.
Mistake #5: environment variables are named differently from the placeholders in YAML.
You wrote ${DB_PASSWORD:shop}, but exported DB_PASS. The application will honestly take the default value and connect with the password shop, while you will be convinced that you "passed the correct password." Here, discipline in naming and a small diagnostic through Environment help, where you can see the final value of the URL and login.
GO TO FULL VERSION