Spring Boot Starter

Spring Boot simplified Spring application development by providing built-in starters. For example, if we want to get started building REST webservice just include the spring-boot-starter-web dependency in your project. If we want to get started using Spring with JDBC for database access, just include the spring-boot-starter-data-jdbc.

Project's dependencies are taken care of by the starters. The developer names it in the dependency section of their project and the build will resolves the required dependencies. Which one simpler, the old ways like this:

<properties> <servlet.api.version>3.0.1</servlet.api.version> <org.springframework.version>5.1.2.RELEASE</org.springframework.version> <org.springframework.security.version>5.1.1.RELEASE</org.springframework.security.version> <jackson2.version>2.9.9.3</jackson2.version> <jackson-jaxrs.version>2.9.9</jackson-jaxrs.version> <lombok.version>1.18.6</lombok.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${org.springframework.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${org.springframework.security.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlet.api.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson2.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-json-provider</artifactId> <version>${jackson-jaxrs.version}</version> </dependency> </dependencies>

Or Spring Boot dependencies:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> </dependencies>

Please note, that I said simpler, not better. Simpler as no more hunting for dependencies, the starters provided by Spring simplified it by following the idea of convention rather than configuration and can help build almost any type of project specification quickly.

Spring Boot Starter Parent

We can see the spring-boot-starter-parent declaration in Spring Boot application's pom.xml:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.8.RELEASE</version> </parent>

The spring-boot-starter-parent project is a special starter project is a convenient and easy way for dependency management and provides default configuration for Maven plugins.

In general, parent pom declarations allows us to manage following things for multiple child projects.

  • Dependency Management: manage dependencies to avoid dependency conflicts.
  • Configuration: such as properties and constants, including versions of artifacts, compiler-settings, etc. It allows us to maintain consistency across all sub projects.
  • Default Plugin Configuration: specifies the default configuration for a host of plugins including maven-failsafe-plugin, maven-jar-plugin and maven-surefire-plugin.

While working on the Spring Boot application, you may not need to provide the version number for the dependencies as these are automatically taken care by Spring Boot. Just pull any dependency from the parent by declaring it in our dependencies tag:

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>

For sure you able to override the version, but this is not necessary (even - not recommended):

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> <version>2.1.9.RELEASE</version> </dependency>

Behind the scene, spring-boot-starter-parent in turn inherited from spring-boot-dependencies

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.1.8.RELEASE</version> <relativePath>../spring-boot-dependencies</relativePath> </parent>

It inherits dependency management from spring-boot-dependencies. This is the actual file which contains the information of default version to use for all libraries. Following snippet shows the different versions of various dependencies that are configured in spring-boot-dependencies 2.1.8.RELEASE:

<properties> ... ... purposely truncated ... <spring.version>5.1.9.RELEASE</spring.version> <spring-amqp.version>2.1.8.RELEASE</spring-amqp.version> <spring-batch.version>4.1.2.RELEASE</spring-batch.version> <spring-cloud-connectors.version>2.0.6.RELEASE</spring-cloud-connectors.version> <spring-data-releasetrain.version>Lovelace-SR10</spring-data-releasetrain.version> <spring-framework.version>${spring.version}</spring-framework.version> <spring-hateoas.version>0.25.2.RELEASE</spring-hateoas.version> <spring-integration.version>5.1.7.RELEASE</spring-integration.version> <spring-kafka.version>2.2.8.RELEASE</spring-kafka.version> <spring-ldap.version>2.3.2.RELEASE</spring-ldap.version> <spring-plugin.version>1.2.0.RELEASE</spring-plugin.version> <spring-restdocs.version>2.0.3.RELEASE</spring-restdocs.version> <spring-retry.version>1.2.4.RELEASE</spring-retry.version> <spring-security.version>5.1.6.RELEASE</spring-security.version> <spring-session-bom.version>Bean-SR7</spring-session-bom.version> <spring-ws.version>3.0.7.RELEASE</spring-ws.version> <sqlite-jdbc.version>3.25.2</sqlite-jdbc.version> <statsd-client.version>3.1.0</statsd-client.version> <sun-mail.version>${javax-mail.version}</sun-mail.version> <thymeleaf.version>3.0.11.RELEASE</thymeleaf.version> <thymeleaf-extras-data-attribute.version>2.0.1</thymeleaf-extras-data-attribute.version> <thymeleaf-extras-java8time.version>3.0.4.RELEASE</thymeleaf-extras-java8time.version> <thymeleaf-extras-springsecurity.version>3.0.4.RELEASE</thymeleaf-extras-springsecurity.version> <thymeleaf-layout-dialect.version>2.3.0</thymeleaf-layout-dialect.version> <tomcat.version>9.0.24</tomcat.version> ... ... purposely truncated ... </properties>

To override default version to use of a dependency, you can override it in properties tag of your project’s pom.xml file:

<properties> <java.version>1.8</java.version> <spring-security.version>5.2.0.RELEASE</spring-security.version> </properties>

After this, the Spring Security used will be version 5.2.0.RELEASE instead of version 5.1.6.RELEASE as in default Spring Boot 2.1.8.RELEASE

Warning: Changing or replace library default version may result in dependencies problem and application not running properly. Override if you are aware what you are doing or what you want to get (gain).

Starters

The starters contain a lot of the dependencies that you need to get a project up and running quickly and with a consistent, supported set of managed transitive dependencies.

Spring boot starters help in resolving dependency by identify required dependencies and import them for you. It helps to avoid dependency conflict since starters has information of compatible version for all dependencies. It minimizes the runtime classloader issues.

There are more than 40 application starters provided by Spring Boot under the org.springframework.boot group. Let see some of them:

Spring Boot Starter Web

It's starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container.

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

With spring-boot-starter-web, we can create a REST controller:

TodoRestController.java
import com.dariawan.todolist.domain.Todo;
import com.dariawan.todolist.exception.BadResourceException;
import com.dariawan.todolist.exception.ResourceNotFoundException;
import com.dariawan.todolist.service.TodoService;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TodoRestController {
   
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @Autowired
    private TodoService todoService;
    
    @GetMapping(value = "/api/todos", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<List<Todo>> findAll() {
        return ResponseEntity.ok(todoService.findAll());
    }

    @GetMapping("/api/todos/{todoId}")
    public ResponseEntity<Todo> findTodoById(@PathVariable long todoId) {
        try {
            Todo book = todoService.findById(todoId);
            return ResponseEntity.ok(book);  // return 200, with json body
        } catch (ResourceNotFoundException ex) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); // return 404, with null body
        }
    }
    
    @PostMapping(value = "/api/todos")
    public ResponseEntity<Todo> addTodo(@RequestBody Todo todo) throws URISyntaxException {
        try {
            Todo newTodo = todoService.save(todo);
            return ResponseEntity.created(new URI("/api/todos/" + newTodo.getTodoId()))
                    .body(todo);
        } catch (BadResourceException ex) {
            // log exception first, then return Bad Request (400)
            logger.error(ex.getMessage());
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
        }
    }
}
                    

This starter is also allow us to change embedded Tomcat server's port, as example via a properties file:

server.port=8888

And the result:

2019-10-08 01:01:21.492 INFO 9396 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8888 (http)

Spring Boot Starter Data JDBC

Spring Data JDBC, help to ease the implementation of JDBC based repositories. This module deals with enhanced support for JDBC based data access layers. It makes it easier to build Spring powered applications that use data access technologies. Instead of defining all of dependencies manually, let's include it starters:

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

In this example, we will use PostgreSQL. Let's see what the Dao layer looks like:

UserDao.java
package com.dariawan.meetdash.dao;

import com.dariawan.meetdash.entity.User;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;

@Repository
public class UserDao extends BaseDao {

    private JdbcTemplate jdbcTemplate;
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    private SimpleJdbcInsert jdbcInsertUser;

    private static final String SQL_UPDATE
            = "update d_user set username=:username, password=:password, "
            + "firstname=:firstname, lastname=:lastname, enabled=:enabled where id = :id";
    private static final String SQL_DELETE = "delete from d_user where id = ?";
    private static final String SQL_FIND_BY_ID = "select * from d_user where id = ?";
    private static final String SQL_FIND_BY_USERNAME = "select * from d_user where username = ?";
    private static final String SQL_FIND_ALL = "select * from d_user limit ? offset ?";
    private static final String SQL_COUNT_ALL = "select count(*) from d_user";

    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
        this.jdbcInsertUser = new SimpleJdbcInsert(dataSource)
                .withTableName("d_user")
                .usingGeneratedKeyColumns("id");
    }

    // User findByUsername(String username);
    public void save(User u) {
        if (u.getId() == null) {
            // create
            SqlParameterSource produkParameter = new BeanPropertySqlParameterSource(u);
            Number newId = jdbcInsertUser.executeAndReturnKey(produkParameter);
            u.setId(newId.intValue());
        } else {
            // update
            SqlParameterSource userParameter = new MapSqlParameterSource()
                    .addValue("id", u.getId())
                    .addValue("username", u.getUsername())
                    .addValue("password", u.getPassword())
                    .addValue("firstname", u.getFirstName())
                    .addValue("lastname", u.getLastName())
                    .addValue("email", u.getEmail())
                    .addValue("enabled", u.isEnabled());
            namedParameterJdbcTemplate.update(SQL_UPDATE, userParameter);
        }
    }

    public void delete(User u) {
        jdbcTemplate.update(SQL_DELETE, u.getId());
    }

    public User findById(Integer id) {
        try {
            return jdbcTemplate.queryForObject(SQL_FIND_BY_ID,
                    new ResultSetToUser(), id);
        } catch (EmptyResultDataAccessException err) {
            return null;
        }
    }

    public User findByUsername(String username) {
        try {
            return jdbcTemplate.queryForObject(SQL_FIND_BY_USERNAME,
                    new ResultSetToUser(), username);
        } catch (EmptyResultDataAccessException err) {
            return null;
        }
    }

    public List<User> findAll(int pageNumber, int rowPerPage) {
        return jdbcTemplate.query(SQL_FIND_ALL,
                new ResultSetToUser(), rowPerPage, getRowStart(pageNumber, rowPerPage));
    }

    public Long count() {
        return jdbcTemplate.queryForObject(SQL_COUNT_ALL, Long.class);
    }
    
    class ResultSetToUser implements RowMapper<User> {

        @Override
        public User mapRow(ResultSet rs, int i) throws SQLException {
            User u = new User();
            u.setId((Integer) rs.getObject("id"));
            u.setUsername(rs.getString("username"));
            u.setPassword(rs.getString("password"));
            u.setFirstName(rs.getString("firstname"));
            u.setLastName(rs.getString("lastName"));
            u.setEmail(rs.getString("email"));
            u.setEnabled(rs.getBoolean("enabled"));
            return u;
        }
    }
}
                    

With BaseDao:

package com.dariawan.meetdash.dao;

public class BaseDao {

    public static Integer getRowStart(int pageNumber, int rowPerPage) {
        if (pageNumber < 1) {
            return 0;
        }
        return (pageNumber - 1) * rowPerPage;
    }
}
                    

And User class is just a simple POJO:

package com.dariawan.meetdash.entity;

import java.io.Serializable;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class User implements Serializable {
    
    private static final long serialVersionUID = 98556721815764L;
    
    private Integer id;
    private String username;
    private String password;
    private String firstName;
    private String lastName;
    private String email;
    private boolean enabled;
}
                    

Conclusion

Spring Boot Starters takes care of the configuration and dependency issues, developers can focus on an application's business functionality with almost no intervention in the configuration and dependency management.