---
# title: Courses
weight: 3
---

# Prerequisites

Before diving in, make sure you're comfortable with the following:

- **Java** — solid understanding of the language
- **Object-oriented programming** — classes, methods and interfaces
- **Databases** — tables, primary keys, foreign keys, relationships, etc.
- **SQL** — ability to write basic SQL statements

---

# What is a Spring Framework?

**Spring** is a popular framework for building Java applications. It has a lot of modules, each designed to handle a specific task. They are combined into a few different layers.

![Spring layers](assets/spring-layers.svg)
*Img. 1 — Spring layers*

| **Layer** | **Purpose** |
|-----------|-------------|
| *Core*    | Handling dependency injection, managing objects |
| *Web*     | Building web applications |
| *Data*    | Working with databases |
| *AOP*     | Aspect oriented programming |
| *Test*    | Testing spring components |

---

While the Spring Framework is powerful, using it often involves a lot of configuration. For example, if you want to build a web app you might need to setup a web server, configure routing and manage dependencies manually. That's when **Spring Boot** comes in.

> [!NOTE]
> You can think of Spring Boot as a layer on top of the Spring Framework that takes care of all of the setup. *Spring Boot* simplifies Spring development by providing sensible defaults and ready-to-use features.

By the way, the Spring Framework is just one part of a larger family of projects in the **Spring ecosystem**.

![Spring ecosystem](assets/spring-ecosystem.svg)
*Img. 2 — Spring ecosystem*

| **Module Name**        | **Purpose** |
|------------------------|-------------|
| *Spring Data*          | Simplifying database access |
| *Spring Security*      | Adding authentication and authorization |
| *Spring Batch*         | Batch processing |
| *Spring Cloud*         | Building microservices and distributed systems |
| *Spring Integration*   | Simplifying messaging and integration between systems |

---

# Initialize Spring Boot Project

To initialize a new Spring Boot project, go to [start.spring.io](https://start.spring.io/) and select the options that suit you.

![Spring Options](assets/spring-project-init.png)
*Img. 3 — Spring Boot options*

After unpacking the `zip` archive, you'll have this template project:

```bash
.
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── tech
│   │   │       └── codejava
│   │   │           └── store
│   │   │               └── StoreApplication.java
│   │   └── resources
│   │       └── application.properties
│   └── test
│       └── java
│           └── tech
│               └── codejava
│                   └── store
│                       └── StoreApplicationTests.java
└── target
    ├── classes
    │   ├── application.properties
    │   └── tech
    │       └── codejava
    │           └── store
    │               └── StoreApplication.class
    ├── generated-sources
    │   └── annotations
    ├── generated-test-sources
    │   └── test-annotations
    ├── maven-status
    │   └── maven-compiler-plugin
    │       ├── compile
    │       │   └── default-compile
    │       │       ├── createdFiles.lst
    │       │       └── inputFiles.lst
    │       └── testCompile
    │           └── default-testCompile
    │               ├── createdFiles.lst
    │               └── inputFiles.lst
    ├── surefire-reports
    │   ├── TEST-tech.codejava.store.StoreApplicationTests.xml
    │   └── tech.codejava.store.StoreApplicationTests.txt
    └── test-classes
        └── tech
            └── codejava
                └── store
                    └── StoreApplicationTests.class
```

The "heart" of our project is `pom.xml`:

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.0.2</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>tech.codejava</groupId>
    <artifactId>store</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>store</name>
    <description>Store</description>
    <url />
    <licenses>
        <license />
    </licenses>
    <developers>
        <developer />
    </developers>
    <scm>
        <connection />
        <developerConnection />
        <tag />
        <url />
    </scm>
    <properties>
        <java.version>21</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
```

Maven uses this file to download dependencies and build our project.

In the `src` folder we have the actual code:

```bash
src
├── main
│   ├── java
│   │   └── tech
│   │       └── codejava
│   │           └── store
│   │               └── StoreApplication.java
│   └── resources
│       └── application.properties
└── test
    └── java
        └── tech
            └── codejava
                └── store
                    └── StoreApplicationTests.java
```

`StoreApplication.java` is the entry point to our application:

```java
package tech.codejava.store;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StoreApplication {

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

}
```

In the `main` method we have a call to `SpringApplication.run`.

Running `mvn clean install` from the root of our project gives us this result *(output partially reduced)*:

```bash
...
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.542 s -- in tech.codejava.store.StoreApplicationTests
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- jar:3.4.2:jar (default-jar) @ store ---
[INFO] Building jar: /home/fymio/store/target/store-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot:4.0.2:repackage (repackage) @ store ---
...
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  14.787 s
[INFO] Finished at: 2026-02-19T13:16:47+03:00
[INFO] ------------------------------------------------------------------------
```

Our application built without errors.

---

# Dependency Management

Dependencies are third-party libraries or frameworks we use in our application. For example, to build a web application we need an embedded web server like *Tomcat*, libraries for handling web requests, building APIs, processing JSON data, logging and so on.

In Spring Boot applications, instead of adding multiple individual libraries, we can use a **starter dependency**.

![Spring Boot Starter Web](assets/spring-boot-starter-web.svg)
*Img. 5 — Spring Boot Starter Web*

To use this dependency, copy the following into your `pom.xml`:

```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>4.1.0-M1</version>
</dependency>
```

So the `dependencies` section would look like this:

```xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!-- <version>4.1.0-M1</version> -->
    </dependency>
</dependencies>
```

> [!IMPORTANT]
> Notice that the version is commented out. It's a better practice to let Spring Boot decide what version of the dependency to use, as it ensures compatibility across your project.

---

# Controllers

**Spring MVC** stands for *Model View Controller*.

- **Model** is where our application's data lives. It represents the business logic and is usually connected to a database or other data sources. In Spring Boot, the model can be a simple Java class.
- **View** is what the user sees. It's the HTML, CSS or JavaScript rendered in the browser. In Spring MVC, views can be static files or dynamically generated.
- **Controller** is like a traffic controller. It handles incoming requests from the user, interacts with the model to get data and then tells the view what to display.

Let's add a new Java class called `HomeController` at `src/main/java/tech/codejava/store/HomeController.java`:

```java
package tech.codejava.store;

public class HomeController {}
```

To make this a controller, decorate it with the `@Controller` annotation:

```java
package tech.codejava.store;

import org.springframework.stereotype.Controller;

@Controller
public class HomeController {}
```

Now let's add an `index` method. When we send a request to the root of our website, we want this method to be called:

```java
package tech.codejava.store;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {

    @RequestMapping("/") // this represents the root of our website
    public String index() {
        return "index.html"; // this returns the view
    }
}
```

Now we need to create the view. Add `index.html` at `src/main/resources/static/index.html`:

```html
<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>View</title>
    </head>
    <body>
        <h1>Hello world!</h1>
    </body>
</html>
```

Let's build and run our application using `mvn spring-boot:run`. From the logs:

```bash
2026-02-19T14:55:23.948+03:00  INFO 36752 --- [store] [           main] o.s.boot.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
```

Our app is up and running at [localhost:8080](http://localhost:8080/).

![Hello world!](assets/hello-world.png)
*Img. 7 — Our app is up and running!*

---

# Configuring Application Properties

Let's take a look at `src/main/resources/application.properties`:

```properties
spring.application.name=store
```

To use this property in our code, we can use the `@Value` annotation. Let's update `HomeController` to print the application name:

```java
package tech.codejava.store;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {

    @Value("${spring.application.name}")
    private String appName;

    @RequestMapping("/") // this represents the root of our website
    public String index() {
        System.out.println("application name = " + appName);
        return "index.html"; // this returns the view
    }
}
```

After running the application, we can see `store` printed in the terminal:

```bash
...
2026-02-19T15:32:37.507+03:00  INFO 41536 --- [store] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2026-02-19T15:32:37.509+03:00  INFO 41536 --- [store] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
application name = store
...
```

---

# Dependency Injection

Imagine we're building an E-Commerce application that handles placing orders. When an order is placed, the customer's payment needs to be processed — so `OrderService` depends on a payment service like `StripePaymentService`. We can say that `OrderService` is *dependent on* (or *coupled to*) `StripePaymentService`.

![Depends on/Coupled to relation](assets/depends-on-coupled-to.svg)
*Img. 8 — Depends On/Coupled To relation*

Let's talk about the issues that arise when one class is **tightly coupled** to another.

1. **Inflexibility** — `OrderService` can only use `StripePaymentService`. If tomorrow we decide to switch to a different payment provider like PayPal, we would have to modify `OrderService`. Once we change it, it has to be recompiled and retested, which could impact other classes that depend on it.
2. **Untestability** — We cannot test `OrderService` in isolation, because `OrderService` is tightly coupled with `StripePaymentService` and we can't test its logic separately from it.

> [!NOTE]
> The problem here isn't that `OrderService` *depends* on `StripePaymentService` — dependencies are normal in any application. The issue is about *how* the dependency is created and managed.

**Analogy:** Think of a restaurant. A restaurant needs a chef — that's a perfectly normal dependency. If the current chef becomes unavailable, the restaurant can hire another one.

![Restaurant — Chef dependency](assets/restaurant-chef-dependency.svg)
*Img. X — Restaurant — Chef dependency (Normal)*

Now what if we replace "chef" with a specific person: John? Our restaurant is now dependent on *John specifically*. If John becomes unavailable, we can't replace him — the restaurant is in trouble. This is an example of **tight** or **bad coupling**.

![Restaurant — John dependency](assets/restaurant-john-dependency.svg)
*Img. X — Restaurant — John dependency (Bad coupling)*

We don't want `OrderService` to be tightly coupled to a specific payment service like Stripe. Instead, we want it to depend on a `PaymentService` *interface*, which could be Stripe, PayPal, or any other provider. To achieve this we can use the *interface* to decouple `OrderService` from `StripePaymentService`.

![Payment Service as `interface`](assets/payment-service-as-interface.svg)
*Img. X — `PaymentService` as `interface`*

If `OrderService` depends on a `PaymentService` interface, it doesn't know anything about Stripe, PayPal, or any other payment provider. As long as these providers implement `PaymentService`, they can be used to handle payments — and `OrderService` won't care which one is being used.

**Benefits:**

1. If we replace `StripePaymentService` with `PayPalPaymentService`, the `OrderService` class is not affected.
2. We don't need to modify or recompile `OrderService`.
3. We can test `OrderService` in isolation, without relying on the specific payment provider like Stripe.

With this setup, we simply give `OrderService` a particular implementation of `PaymentService`. This is called **dependency injection** — we *inject* the dependency into a class.

![Dependency Injection example](assets/dependency-injection.svg)
*Img. X — Dependency Injection example*

Let's see how it works in our project. Create `OrderService` at `src/main/java/tech/codejava/store/OrderService.java`:

```java
package tech.codejava.store;

public class OrderService {

    public void placeOrder() {}
}
```

> [!NOTE]
> In a real project we would need to provide something like `Order order` to this method, but for teaching purposes we won't do that.

Now create `StripePaymentService` in the same directory:

```java
package tech.codejava.store;

public class StripePaymentService {

    public void processPayment(double amount) {
        System.out.println("=== STRIPE ===");
        System.out.println("amount: " + amount);
    }
}
```

Let's implement `placeOrder` in `OrderService` using `StripePaymentService`:

```java
package tech.codejava.store;

public class OrderService {

    public void placeOrder() {
        var paymentService = new StripePaymentService();
        paymentService.processPayment(10);
    }
}
```

> [!IMPORTANT]
> This is our *before* setup — before we introduced the interface. In this implementation, `OrderService` is **tightly coupled** to `StripePaymentService`. We cannot test `OrderService` in isolation, and switching to another payment provider would require modifying `OrderService`.

Let's fix this. Create a `PaymentService` interface in the same directory:

```java
package tech.codejava.store;

public interface PaymentService {
    void processPayment(double amount);
}
```

Modify `StripePaymentService` to implement `PaymentService`:

```java
package tech.codejava.store;

public class StripePaymentService implements PaymentService {

    @Override
    public void processPayment(double amount) {
        System.out.println("=== STRIPE ===");
        System.out.println("amount: " + amount);
    }
}
```

The recommended way to inject a dependency into a class is via its **constructor**. Let's define one in `OrderService`:

```java
package tech.codejava.store;

public class OrderService {

    private PaymentService paymentService;

    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void placeOrder() {
        paymentService.processPayment(10);
    }
}
```

Now let's see this in action. Modify `StoreApplication`:

```java
package tech.codejava.store;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StoreApplication {

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

        var orderService = new OrderService(new StripePaymentService());
        orderService.placeOrder();
    }
}
```

Running the application *(output intentionally reduced)*:

```bash
...
=== STRIPE ===
amount: 10.0
...
```

Now let's create a `PayPalPaymentService` in the same directory:

```java
package tech.codejava.store;

public class PayPalPaymentService implements PaymentService {

    @Override
    public void processPayment(double amount) {
        System.out.println("=== PayPal ===");
        System.out.println("amount: " + amount);
    }
}
```

Now we can switch from `StripePaymentService` to `PayPalPaymentService` in `StoreApplication` — without touching `OrderService` at all:

```java
package tech.codejava.store;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StoreApplication {

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

        // var orderService = new OrderService(new StripePaymentService());
        var orderService = new OrderService(new PayPalPaymentService());
        orderService.placeOrder();
    }
}
```

```bash
...
=== PayPal ===
amount: 10.0
...
```

Notice that we didn't change `OrderService`. In *object-oriented programming* this is known as the **Open/Closed Principle**:

> A class should be open for extension and closed for modification.

In other words: we should be able to add new functionality to a class without changing its existing code. This reduces the risk of introducing bugs and breaking other parts of the application.

---

## Setter Injection

Another way to inject a dependency is via a **setter**. In `OrderService`, let's define one:

```java
package tech.codejava.store;

public class OrderService {

    private PaymentService paymentService;

    public void setPaymentService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void placeOrder() {
        paymentService.processPayment(10);
    }
}
```

We can use it like this in `StoreApplication`:

```java
package tech.codejava.store;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StoreApplication {

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

        // var orderService = new OrderService(new StripePaymentService());
        var orderService = new OrderService(new PayPalPaymentService());
        orderService.setPaymentService(new PayPalPaymentService());
        orderService.placeOrder();
    }
}
```

> [!IMPORTANT]
> If you remove the constructor from `OrderService` and forget to call the setter, the application will crash with a `NullPointerException`. Use setter injection only for **optional** dependencies — ones that `OrderService` can function without.
