How do I get started with Spring MVC?

Spring MVC is a web framework built on top of the Spring Framework. It helps developers create web applications and REST APIs using the Model-View-Controller (MVC) architectural pattern.

In this tutorial, we will introduce Spring MVC, explore its main components, and understand how an HTTP request is processed.

1. What is Spring MVC?

Spring MVC is part of the Spring Framework and provides a structured way to handle HTTP requests, process application logic, and generate responses.

It follows the Model-View-Controller pattern, which separates an application into three main responsibilities:

  • Model: Represents application data and business information.
  • View: Displays information to users, typically as HTML.
  • Controller: Receives HTTP requests and coordinates the application’s response.

This separation makes applications easier to develop, maintain, and test.

Spring MVC can also build REST APIs that return JSON instead of HTML.

2. How does Spring MVC work?

The central component of Spring MVC is the DispatcherServlet. It acts as a front controller that receives incoming HTTP requests and delegates them to the appropriate controller.

Spring MVC Request Flow Diagram

When a browser sends an HTTP request, Spring MVC performs the following steps:

  1. The DispatcherServlet receives the request.
  2. Spring MVC identifies the appropriate controller method.
  3. The controller processes the request, often calling application services.
  4. The controller returns data or a view name.
  5. Spring MVC generates an HTTP response.

For HTML pages, a view resolver can help locate the appropriate view template. For REST APIs, an HTTP message converter can serialize Java objects into JSON.

3. Creating a simple Spring MVC controller

Let’s look at a simple controller that returns a greeting.

package org.kodejava.springmvc;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String sayHello() {
        return "Hello, Spring MVC!";
    }
}

The example uses two important annotations:

  • @RestController tells Spring that the class handles web requests and writes return values directly to the HTTP response body.
  • @GetMapping("/hello") maps HTTP GET requests for /hello to the sayHello() method.

When the application is properly configured and running, accessing /hello returns:

Hello, Spring MVC!

This controller is only one part of a Spring MVC application. We still need to configure the application context and register the DispatcherServlet before it can handle requests.

We will implement these components in the next tutorial.

4. Spring MVC vs. Spring Boot

Spring MVC and Spring Boot are related, but they serve different purposes.

Spring MVC Spring Boot
Web application framework Application configuration and bootstrapping
Provides controllers and request mappings Provides auto-configuration
Can be configured manually Simplifies application setup
Supports HTML and REST APIs Can run Spring MVC with an embedded server

Spring Boot does not replace Spring MVC. Instead, Spring Boot can configure and run Spring MVC applications with less setup.

5. Summary

In this tutorial, we learned that Spring MVC is a web framework based on the MVC architectural pattern. We also introduced the DispatcherServlet, explored the request processing flow, and created a simple REST controller.

In the next tutorial, How do I create my first Spring MVC application?, we will build a complete application using Java configuration and Maven.

6. Maven Dependencies

This series uses Spring Framework 7.0.10, the latest stable 7.0 release verified for November 10, 2026. Spring Framework 7.1 is still in its release-candidate stage.

Add the following dependency to your Maven pom.xml:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>7.0.10</version>
</dependency>

The dependency provides Spring MVC’s core functionality. A runnable web application will additionally need a compatible Jakarta Servlet environment, which we will configure later in this series.

How do I compare two strings in Java?

Use the equals() method to check whether two strings contain the same text. Use equalsIgnoreCase() when capitalization should not affect the comparison.

In this example, you will compare strings with identical text and strings with different capitalization.

Source code

Save the following code as StringComparisonExample.java:

public class StringComparisonExample {
    void main() {
        String first = "Java";
        String second = new String("Java");
        String third = "java";

        IO.println("Same text: " + first.equals(second));
        IO.println("Same object: " + (first == second));
        IO.println("Same case-sensitive text: " + first.equals(third));
        IO.println("Same text ignoring case: "
                + first.equalsIgnoreCase(third));
    }
}

Explanation

The example creates three string variables:

String first = "Java";
String second = new String("Java");
String third = "java";

The first two strings contain identical text. The third starts with a lowercase letter.

The explicit new String("Java") creates a separate object to demonstrate the difference between comparing text and comparing references. In ordinary code, prefer a string literal when you do not need a separate object.

Comparing text with equals()

The equals() method compares the contents of strings:

first.equals(second)

This returns true because both strings contain "Java".

The comparison is case-sensitive:

first.equals(third)

This returns false because "Java" and "java" have different capitalization.

Comparing references with ==

For strings, the == operator checks whether two references point to the same object:

first == second

This returns false because second refers to a separately created string object, even though the text is identical.

String literals may share an object, so == can sometimes return true for strings containing the same text. Always use equals() when your intention is to compare their contents.

Ignoring capitalization

Use equalsIgnoreCase() when letter case should not affect the result:

first.equalsIgnoreCase(third)

This returns true for "Java" and "java".

Run the example

Compile and run the file:

javac StringComparisonExample.java
java StringComparisonExample

Output

Same text: true
Same object: false
Same case-sensitive text: false
Same text ignoring case: true

Handling null values

Calling equals() on a variable containing null throws a NullPointerException.

When comparing a variable with a known string literal, put the literal first:

"Java".equals(value)

This returns false if value is null.

If either of two variables may be null, use Objects.equals():

Objects.equals(firstValue, secondValue)

Import java.util.Objects to use this method. It returns true when both arguments are null, and otherwise compares their values safely.

How do I use a method reference in Java?

A method reference lets you use an existing method as the implementation of a functional interface. It can make a lambda expression shorter when the lambda only calls that method.

In this example, you will use a method reference to print each language in a list.

Source code

Save the following code as MethodReferenceExample.java:

import java.util.List;

public class MethodReferenceExample {
    void main() {
        List<String> languages = List.of(
                "Java", "Python", "JavaScript");

        languages.forEach(IO::println);
    }
}

Explanation

The example creates a list containing three language names:

List<String> languages = List.of(
        "Java", "Python", "JavaScript");

The forEach() method performs an action for each element in the list:

languages.forEach(IO::println);

It accepts a Consumer, a functional interface whose abstract method takes one argument and returns no value.

Referring to an existing method

The expression IO::println is a reference to the static println() method in java.lang.IO.

The double colon (::) separates the class name from the method name. The method reference does not call the method immediately. It supplies the action that forEach() invokes for each element.

This method reference can replace the following lambda expression:

languages.forEach(language -> IO.println(language));

Both versions print each language on a separate line, in list order.

Matching the functional interface

For this list, forEach() needs an action that accepts a string and returns no value.

IO.println(Object) is compatible because it accepts a String as an Object and returns void.

You do not add parentheses after the method name in a method reference:

IO::println

Parentheses belong to a method call, such as:

IO.println("Java");

Run the example

Compile and run the file:

javac MethodReferenceExample.java
java MethodReferenceExample

Output

Java
Python
JavaScript

Use a method reference when an existing method already performs the required action. Keep a lambda expression when you need additional logic, such as adding a prefix before printing each value.

What Is Hibernate ORM?

When you use JDBC to read a row from a database, you normally write a SQL query, execute it, and copy the values from the ResultSet into a Java object. If your application has classes such as Book, Author, and Publisher, you repeat this kind of mapping for each class.

Hibernate ORM is a Java framework that handles much of this mapping. You describe how your Java classes correspond to tables, and Hibernate uses that information to load objects and save changes. This series uses Java 25 and Hibernate ORM 7.4.15.Final, the latest stable release as of November 9, 2026. Hibernate ORM 7.4 implements Jakarta Persistence 3.2. See the Hibernate ORM release notes.

What does ORM mean?

ORM stands for object-relational mapping. It connects the way Java represents data as objects with the way a relational database stores data in tables.

For a simple book catalog, the mapping looks like this.

Java Database
Book class books table
One Book object One row in books
id field id primary key column
title field title column

A class that Hibernate can persist is called an entity. Here is the first entity in our catalog.

package org.kodejava.hibernate;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "books")
public class Book {
    @Id
    private Long id;

    @Column(nullable = false)
    private String title;

    protected Book() {
    }

    public Book(Long id, String title) {
        this.id = id;
        this.title = title;
    }

    public String getTitle() {
        return title;
    }
}
  • @Entity marks Book as a persistent entity.
  • @Table(name = "books") maps the entity to the books table.
  • @Id identifies the field that maps to the primary key. This example assigns the ID in the constructor.
  • @Column(nullable = false) declares the title column as non-nullable in the mapping.

Hibernate can read and write the mapped fields directly because the mapping annotations are on the fields. The protected no-argument constructor allows the persistence provider to instantiate a Book when it loads data.

How does Hibernate save an object?

After Hibernate is configured, you can use an EntityManager to work with entities. The following snippet assumes an existing EntityManager and an active transaction.

Book book = new Book(1L, "Effective Java");
entityManager.persist(book);

Calling persist() makes the new Book a managed entity. Hibernate now tracks it in the persistence context. When Hibernate flushes the context, typically as part of committing the transaction, it executes an SQL statement similar to this one.

insert into books (title, id) values (?, ?)

Hibernate binds the values from the object to those parameters and executes the statement through JDBC. You can load a book by its primary key with entityManager.find(Book.class, 1L). If the persistence context already manages that book, Hibernate can return it directly. Otherwise, Hibernate can load it from the database, and find() returns null if the book does not exist.

What is the difference between Jakarta Persistence and Hibernate?

Jakarta Persistence, often called JPA, defines standard annotations and interfaces such as @Entity, @Id, and EntityManager. Hibernate ORM implements that standard and provides its own API, including SessionFactory and Session.

We will start with Jakarta Persistence so you can learn the standard mapping and transaction APIs. Later articles will use Hibernate-specific features where they help solve a concrete problem, such as inspecting SQL activity or tuning fetch behavior. The Hibernate ORM Javadocs describe both APIs.

Where does JDBC fit?

Hibernate uses JDBC to communicate with the database. With plain JDBC, you usually write the SQL and map the results yourself. With Hibernate, you provide entity mappings, and Hibernate handles many routine operations, such as inserting a row or finding it by primary key.

The SQL still matters. Loading a list of books and then accessing each book’s author can trigger extra queries. Later in the series, we will inspect the generated SQL and see how a fetch join can avoid that pattern.

In the next article, we will create a Maven project, configure an in-memory H2 database, and run the Book example from start to finish. We will use plain Java to make the Hibernate configuration and transaction boundaries visible.

Maven Dependencies

Add the following dependency inside the <dependencies> section of your pom.xml. Hibernate Core brings in the Jakarta Persistence API transitively, including the annotations used above. To run the example against a database, you will also need a JDBC driver. We will add the H2 driver in the next article.

<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>7.4.15.Final</version>
</dependency>

How do I calculate a moving average with Stream Gatherers in Java 24?

A moving average smooths out short-term changes in a series of readings. Instead of averaging all the readings once, you average the first few readings, move one position forward, and repeat.

In this example, you’ll calculate an average for every three consecutive temperature readings using Java’s Gatherers.windowSliding().

Stream Gatherers became a standard feature in Java 24, so no preview flags or external libraries are needed. See the Java 24 Gatherers API.

1. Group the readings into overlapping windows

Start with five readings: 18, 21, 24, 27, 23. A window size of three gives you [18, 21, 24] first. The next window drops 18 and adds 27, giving you [21, 24, 27].

The call .gather(Gatherers.windowSliding(3)) turns a stream of individual readings into a stream of these lists.

2. Keep complete windows and calculate each average

If the input has one or two readings, windowSliding(3) produces a single shorter window. The filter() below skips that window so every reported average uses exactly three readings. Empty input produces no windows.

For each remaining list, mapToInt() creates a stream of integers and average() computes the mean. The result is present because every window that reaches this step contains three values.

3. Compile and run the example

Save this file as MovingAverageExample.java:

import java.util.List;
import java.util.Locale;
import java.util.stream.Gatherers;

public class MovingAverageExample {
    public static void main(String[] args) {
        List<Integer> readings = List.of(18, 21, 24, 27, 23);
        int windowSize = 3;

        readings.stream()
                .gather(Gatherers.windowSliding(windowSize))
                .filter(window -> window.size() == windowSize)
                .forEachOrdered(window -> {
                    double average = window.stream()
                            .mapToInt(Integer::intValue)
                            .average()
                            .orElseThrow();

                    System.out.printf(Locale.ROOT, "%s -> %.2f%n",
                            window, average);
                });
    }
}

From the directory containing the file, run:

javac --release 24 MovingAverageExample.java
java MovingAverageExample

Output:

[18, 21, 24] -> 21.00
[21, 24, 27] -> 24.00
[24, 27, 23] -> 24.67

The last window averages 74 / 3. The %.2f format rounds the displayed value to two decimal places; it does not change the stored average.

Best practices

  • Put readings in chronological order before making windows. The gatherer follows the stream’s existing order.
  • Use a positive window size. If it comes from user input, validate it before creating the gatherer.
  • Decide what to do when there are too few readings. This example waits for a full window; if you need early results, define how partial averages should work.
  • A three-reading window counts samples. If your readings arrive at irregular intervals and you need a three-minute average, group them by timestamps instead.