Row Mapping

A database query returns rows of columns, but your Java code works with objects like Student. Row mapping is the step that converts each row into an object. In JdbcTemplate, a RowMapper does this: Spring runs the query, calls your mapper once per row, and collects the objects into a List. It is used so that all the code that reads columns (rs.getString("full_name")) lives in one place, and the rest of the application deals only with Java objects. Without it, you would loop over a ResultSet by hand and repeat that code in every query.

Features

  • One method call per row: you only describe how to convert a single row.
  • Reusable: the same RowMapper works for any query that returns the same columns.
  • Handles the looping and the closing of the ResultSet for you.
  • Column names can differ from property names (full_name to fullName).
  • Works for whole objects, single values, and grouped results.

Row Mapping Types

Type What it does When to use it
1. RowMapper class A separate class implementing RowMapper<Student> with a mapRow method The same mapping is needed in many queries
2. RowMapper lambda The same mapRow logic written inline, with no extra class A mapping needed in just one place
3. BeanPropertyRowMapper Maps columns to properties automatically by name, and full_name becomes fullName Column and property names match, and you want the least code
4. SingleColumnRowMapper Maps a one-column result to simple values like String or Long Queries such as “all names” or “all ids”
5. ResultSetExtractor You receive the whole ResultSet and loop through it yourself to build one result Combined results, such as a Map grouping students by department

Hands-on Experiment: Creating a spring boot application using Row Mapping

The following steps are involved in developing this hands-on experiment.

Step 1: Generate the Project

Step 2: Project Structure

Step 3: pom.xml

Step 4: application.yml

Step 5: Create Entity Class

Step 6: Create Repository Interface

Step 7: Create Service Class

Step 8: Create Controller Class

Step 9: Main Application Class

Step 10: Run the Main Application Class

Step 1: Configure the Project on Spring Initializr

Go to start.spring.io and set:

  • Project: Maven
  • Language: Java
  • Spring Boot version: 3.2.x
  • Group: com.example
  • Artifact: rowmapping
  • Name: rowmapping
  • Package name: com.example.rowmapping
  • Packaging: Jar
  • Java: 17
  • Dependencies: Spring Web, JDBC API, PostgreSQL Driver

Click Generate to download the ZIP, then extract and import it into your IDE as a Maven project.

Step 2: Generated Project Structure

Step 3: Explore pom.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>3.3.5</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>student-rowmapping-demo</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

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

Step 4: application.yml

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/student_jdbc_db
    username: postgres
    password: password
    driver-class-name: org.postgresql.Driver
  sql:
    init:
      mode: always        # runs schema.sql and data.sql at startup

server:
  port: 8082

Step 5: Create Entity Class

package com.example.rowmapping.model;

public class Student {

    private Long id;
    private String fullName;     // column: full_name
    private String email;
    private String department;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getFullName() { return fullName; }
    public void setFullName(String fullName) { this.fullName = fullName; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public String getDepartment() { return department; }
    public void setDepartment(String department) { this.department = department; }
}

Row Mapper Class

package com.example.rowmapping.mapper;

import com.example.rowmapping.model.Student;
import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

// Converts ONE row of the ResultSet into ONE Student object.
// Spring calls mapRow() once for every row and collects the results in a List.
public class StudentRowMapper implements RowMapper<Student> {

    @Override
    public Student mapRow(ResultSet rs, int rowNum) throws SQLException {
        Student student = new Student();
        student.setId(rs.getLong("id"));
        student.setFullName(rs.getString("full_name"));   // column name -> property
        student.setEmail(rs.getString("email"));
        student.setDepartment(rs.getString("department"));
        return student;
    }
}

Step 6: Create below Repository Interface

package com.example.rowmapping.repository;

import com.example.rowmapping.mapper.StudentRowMapper;
import com.example.rowmapping.model.Student;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SingleColumnRowMapper;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Repository
public class StudentRepository {

    private final JdbcTemplate jdbcTemplate;

    public StudentRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    // Used only to add data for the experiment
    public int save(Student student) {
        return jdbcTemplate.update(
                "INSERT INTO student_records (full_name, email, department) VALUES (?, ?, ?)",
                student.getFullName(), student.getEmail(), student.getDepartment());
    }

    // ---------- TYPE 1: RowMapper class ----------
    public List<Student> findAll() {
        return jdbcTemplate.query(
                "SELECT id, full_name, email, department FROM student_records ORDER BY id",
                new StudentRowMapper());
    }

    // ---------- TYPE 2: RowMapper as a lambda (inline) ----------
    public Optional<Student> findById(Long id) {
        RowMapper<Student> inlineMapper = (rs, rowNum) -> {
            Student s = new Student();
            s.setId(rs.getLong("id"));
            s.setFullName(rs.getString("full_name"));
            s.setEmail(rs.getString("email"));
            s.setDepartment(rs.getString("department"));
            return s;
        };
        List<Student> result = jdbcTemplate.query(
                "SELECT id, full_name, email, department FROM student_records WHERE id = ?",
                inlineMapper, id);
        return result.stream().findFirst();
    }

    // ---------- TYPE 3: BeanPropertyRowMapper (automatic) ----------
    // Matches columns to properties by name; full_name is matched to fullName automatically.
    public List<Student> findAllAuto() {
        return jdbcTemplate.query(
                "SELECT id, full_name, email, department FROM student_records ORDER BY id",
                new BeanPropertyRowMapper<>(Student.class));
    }

    // ---------- TYPE 4: SingleColumnRowMapper (one column -> simple values) ----------
    public List<String> findAllNames() {
        return jdbcTemplate.query(
                "SELECT full_name FROM student_records ORDER BY id",
                new SingleColumnRowMapper<>(String.class));
    }

    // ---------- TYPE 5: ResultSetExtractor (whole ResultSet -> one object) ----------
    // You loop through the rows yourself and build ONE result: a Map of department -> students.
    public Map<String, List<Student>> groupByDepartment() {
        StudentRowMapper rowMapper = new StudentRowMapper();

        ResultSetExtractor<Map<String, List<Student>>> extractor = rs -> {
            Map<String, List<Student>> grouped = new LinkedHashMap<>();
            int rowNum = 0;
            while (rs.next()) {
                Student student = rowMapper.mapRow(rs, rowNum++);
                grouped.computeIfAbsent(student.getDepartment(), key -> new ArrayList<>()).add(student);
            }
            return grouped;
        };

        return jdbcTemplate.query(
                "SELECT id, full_name, email, department FROM student_records ORDER BY department, id",
                extractor);
    }
}

Step 7: Create below Service class

package com.example.rowmapping.service;

import com.example.rowmapping.model.Student;
import com.example.rowmapping.repository.StudentRepository;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.Optional;

@Service
public class StudentService {

    private final StudentRepository repository;

    public StudentService(StudentRepository repository) {
        this.repository = repository;
    }

    public void create(Student student) {
        repository.save(student);
    }

    public List<Student> findAll() {
        return repository.findAll();
    }

    public Optional<Student> findById(Long id) {
        return repository.findById(id);
    }

    public List<Student> findAllAuto() {
        return repository.findAllAuto();
    }

    public List<String> findAllNames() {
        return repository.findAllNames();
    }

    public Map<String, List<Student>> groupByDepartment() {
        return repository.groupByDepartment();
    }
}

Step 8: Create Controller class

package com.example.rowmapping.controller;

import com.example.rowmapping.model.Student;
import com.example.rowmapping.service.StudentService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/students")
public class StudentController {

    private final StudentService service;

    public StudentController(StudentService service) {
        this.service = service;
    }

    // Adds a row so you have more data to map
    @PostMapping
    public ResponseEntity<String> create(@RequestBody Student student) {
        service.create(student);
        return ResponseEntity.status(HttpStatus.CREATED).body("Student added");
    }

    // TYPE 1: RowMapper class   (THE EXPERIMENT)
    @GetMapping
    public List<Student> getAll() {
        return service.findAll();
    }

    // TYPE 2: RowMapper lambda
    @GetMapping("/{id}")
    public ResponseEntity<Student> getById(@PathVariable Long id) {
        return service.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    // TYPE 3: BeanPropertyRowMapper
    @GetMapping("/auto")
    public List<Student> getAllAuto() {
        return service.findAllAuto();
    }

    // TYPE 4: SingleColumnRowMapper
    @GetMapping("/names")
    public List<String> getNames() {
        return service.findAllNames();
    }

    // TYPE 5: ResultSetExtractor
    @GetMapping("/grouped")
    public Map<String, List<Student>> getGrouped() {
        return service.groupByDepartment();
    }
}

Step 9: Main Application

package com.example.rowmapping;

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

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

Step 10: Run the RowMappingApplication

Scroll to Top