www.baeldung.com Open in urlscan Pro
2606:4700:3108::ac42:2b08  Public Scan

Submitted URL: http://www.baeldung.com/spring-boot-start
Effective URL: https://www.baeldung.com/spring-boot-start
Submission: On January 27 via manual from US — Scanned from DE

Form analysis 0 forms found in the DOM

Text Content

WE VALUE YOUR PRIVACY

We and our partners store and/or access information on a device, such as cookies
and process personal data, such as unique identifiers and standard information
sent by a device for personalised ads and content, ad and content measurement,
and audience insights, as well as to develop and improve products. With your
permission we and our partners may use precise geolocation data and
identification through device scanning. You may click to consent to our and our
partners’ processing as described above. Alternatively you may access more
detailed information and change your preferences before consenting or to refuse
consenting. Please note that some processing of your personal data may not
require your consent, but you have a right to object to such processing. Your
preferences will apply to this website only. You can change your preferences at
any time by returning to this site or visit our privacy policy.
MORE OPTIONSAGREE

 * 
 * 
 * Start Here
 * Courses ▼▲
   
   
    * REST WITH SPRING
      
      The canonical reference for building a production grade API with Spring
   
   
    * LEARN SPRING SECURITY ▼▲
      
      THE unique Spring Security education if you’re working with Java today
      
      
       * LEARN SPRING SECURITY CORE
         
         Focus on the Core of Spring Security 5
      
      
       * LEARN SPRING SECURITY OAUTH
         
         Focus on the new OAuth2 stack in Spring Security 5
   
   
    * LEARN SPRING
      
      From no experience to actually building stuff
   
   
    * LEARN SPRING DATA JPA
      
      The full guide to persistence with Spring Data JPA

 * Guides ▼▲
   
   
    * PERSISTENCE
      
      The Persistence with Spring guides
   
   
    * REST
      
      The guides on building REST APIs with Spring
   
   
    * SECURITY
      
      The Spring Security guides

 * About ▼▲
   
   
    * FULL ARCHIVE
      
      The high level overview of all the articles on the site.
   
   
    * BAELDUNG EBOOKS
      
      Discover all of our eBooks
   
   
    * ABOUT BAELDUNG
      
      About Baeldung.

 * 
 * 




SPRING BOOT TUTORIAL – BOOTSTRAP A SIMPLE APPLICATION

Last modified: November 17, 2022

by baeldung
 * Spring Boot

 * Boot Basics

Generic Top


GET STARTED WITH SPRING 5 AND SPRING BOOT 2, THROUGH THE LEARN SPRING COURSE:

>> CHECK OUT THE COURSE



1. OVERVIEW



Spring Boot is an opinionated addition to the Spring platform, focused on
convention over configuration — highly useful for getting started with minimum
effort and creating standalone, production-grade applications.

This tutorial is a starting point for Boot, in other words, a way to get started
in a simple manner with a basic web application.

We'll go over some core configuration, a front-end, quick data manipulation, and
exception handling.


FURTHER READING:


HOW TO CHANGE THE DEFAULT PORT IN SPRING BOOT

Have a look at how you can change the default port in a Spring Boot application.
Read more →


INTRO TO SPRING BOOT STARTERS

A quick overview of the most common Spring Boot Starters, along with examples on
how to use them in a real-world project.
Read more →


2. SETUP



First, let's use Spring Initializr to generate the base for our project.

The generated project relies on the Boot parent:



<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.2</version>
    <relativePath />
</parent>Copy

The initial dependencies are going to be quite simple:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>Copy


3. APPLICATION CONFIGURATION



Next, we'll configure a simple main class for our application:

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

Notice how we're using @SpringBootApplication as our primary application
configuration class. Behind the scenes, that's equivalent to @Configuration,
@EnableAutoConfiguration, and @ComponentScan together.

Finally, we'll define a simple application.properties file, which for now only
has one property:

server.port=8081
Copy

server.port changes the server port from the default 8080 to 8081; there are of
course many more Spring Boot properties available.




4. SIMPLE MVC VIEW



Let's now add a simple front end using Thymeleaf.

First, we need to add the spring-boot-starter-thymeleaf dependency to our
pom.xml:

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

That enables Thymeleaf by default. No extra configuration is necessary.

We can now configure it in our application.properties:

spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true 
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

spring.application.name=Bootstrap Spring Boot
Copy

Next, we'll define a simple controller and a basic home page with a welcome
message:



@Controller
public class SimpleController {
    @Value("${spring.application.name}")
    String appName;

    @GetMapping("/")
    public String homePage(Model model) {
        model.addAttribute("appName", appName);
        return "home";
    }
}
Copy

Finally, here is our home.html:

<html>
<head><title>Home Page</title></head>
<body>
<h1>Hello !</h1>
<p>Welcome to <span th:text="${appName}">Our App</span></p>
</body>
</html>
Copy

Note how we used a property we defined in our properties and then injected that
so we can show it on our home page.


5. SECURITY



Next, let's add security to our application by first including the security
starter:

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

By now, we can notice a pattern: Most Spring libraries are easily imported into
our project with the use of simple Boot starters.

Once the spring-boot-starter-security dependency is on the classpath of the
application, all endpoints are secured by default, using either httpBasic or
formLogin based on Spring Security's content negotiation strategy.

That's why, if we have the starter on the classpath, we should usually define
our own custom Security configuration:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .anyRequest()
            .permitAll()
            .and()
            .csrf()
            .disable();
        return http.build();
    }
}
Copy

In our example, we're allowing unrestricted access to all endpoints.

Of course, Spring Security is an extensive topic and not easily covered in a
couple of lines of configuration. So, we definitely encourage deeper reading
into the topic.




6. SIMPLE PERSISTENCE



Let's start by defining our data model, a simple Book entity:

@Entity
public class Book {
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(nullable = false, unique = true)
    private String title;

    @Column(nullable = false)
    private String author;
}Copy

and its repository, making good use of Spring Data here:

public interface BookRepository extends CrudRepository<Book, Long> {
    List<Book> findByTitle(String title);
}Copy

Finally, we need to of course configure our new persistence layer:

@EnableJpaRepositories("com.baeldung.persistence.repo") 
@EntityScan("com.baeldung.persistence.model")
@SpringBootApplication 
public class Application {
   ...
}Copy

Note that we're using the following:

 * @EnableJpaRepositories to scan the specified package for repositories
 * @EntityScan to pick up our JPA entities

To keep things simple, we're using an H2 in-memory database here. This is so
that we don't have any external dependencies when we run the project.

Once we include H2 dependency, Spring Boot auto-detects it and sets up our
persistence with no need for extra configuration, other than the data source
properties:

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:bootapp;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=
Copy

Of course, like security, persistence is a broader topic than this basic set
here and one to certainly explore further.


7. WEB AND THE CONTROLLER



Next, let's have a look at a web tier. And we'll start by setting up a simple
controller, the BookController.



We'll implement basic CRUD operations exposing Book resources with some simple
validation:

@RestController
@RequestMapping("/api/books")
public class BookController {

    @Autowired
    private BookRepository bookRepository;

    @GetMapping
    public Iterable findAll() {
        return bookRepository.findAll();
    }

    @GetMapping("/title/{bookTitle}")
    public List findByTitle(@PathVariable String bookTitle) {
        return bookRepository.findByTitle(bookTitle);
    }

    @GetMapping("/{id}")
    public Book findOne(@PathVariable Long id) {
        return bookRepository.findById(id)
          .orElseThrow(BookNotFoundException::new);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Book create(@RequestBody Book book) {
        return bookRepository.save(book);
    }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        bookRepository.findById(id)
          .orElseThrow(BookNotFoundException::new);
        bookRepository.deleteById(id);
    }

    @PutMapping("/{id}")
    public Book updateBook(@RequestBody Book book, @PathVariable Long id) {
        if (book.getId() != id) {
          throw new BookIdMismatchException();
        }
        bookRepository.findById(id)
          .orElseThrow(BookNotFoundException::new);
        return bookRepository.save(book);
    }
}
Copy

Given this aspect of the application is an API, we made use of the
@RestController annotation here — which is equivalent to a @Controller along
with @ResponseBody — so that each method marshals the returned resource right to
the HTTP response.

Note that we're exposing our Book entity as our external resource here. That's
fine for this simple application, but in a real-world application, we'll
probably want to separate these two concepts.


8. ERROR HANDLING



Now that the core application is ready to go, let's focus on a simple
centralized error handling mechanism using @ControllerAdvice:

@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler({ BookNotFoundException.class })
    protected ResponseEntity<Object> handleNotFound(
      Exception ex, WebRequest request) {
        return handleExceptionInternal(ex, "Book not found", 
          new HttpHeaders(), HttpStatus.NOT_FOUND, request);
    }

    @ExceptionHandler({ BookIdMismatchException.class, 
      ConstraintViolationException.class, 
      DataIntegrityViolationException.class })
    public ResponseEntity<Object> handleBadRequest(
      Exception ex, WebRequest request) {
        return handleExceptionInternal(ex, ex.getLocalizedMessage(), 
          new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
    }
}
Copy

Beyond the standard exceptions we're handling here, we're also using a custom
exception, BookNotFoundException:

public class BookNotFoundException extends RuntimeException {

    public BookNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
    // ...
}
Copy

This gives us an idea of what's possible with this global exception handling
mechanism. To see a full implementation, have a look at the in-depth tutorial.

Note that Spring Boot also provides an /error mapping by default. We can
customize its view by creating a simple error.html:

<html lang="en">
<head><title>Error Occurred</title></head>
<body>
    <h1>Error Occurred!</h1>    
    <b>[<span th:text="${status}">status</span>]
        <span th:text="${error}">error</span>
    </b>
    <p th:text="${message}">message</p>
</body>
</html>Copy

Like most other aspects in Boot, we can control that with a simple property:



server.error.path=/error2Copy


9. TESTING



Finally, let's test our new Books API.

We can make use of @SpringBootTest to load the application context and verify
that there are no errors when running the app:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringContextTest {

    @Test
    public void contextLoads() {
    }
}Copy

Next, let's add a JUnit test that verifies the calls to the API we've written,
using REST Assured.

First, we'll add the rest-assured dependency:

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>Copy

And now we can add the test:

public class SpringBootBootstrapLiveTest {

    private static final String API_ROOT
      = "http://localhost:8081/api/books";

    private Book createRandomBook() {
        Book book = new Book();
        book.setTitle(randomAlphabetic(10));
        book.setAuthor(randomAlphabetic(15));
        return book;
    }

    private String createBookAsUri(Book book) {
        Response response = RestAssured.given()
          .contentType(MediaType.APPLICATION_JSON_VALUE)
          .body(book)
          .post(API_ROOT);
        return API_ROOT + "/" + response.jsonPath().get("id");
    }
}
Copy

First, we can try to find books using variant methods:

@Test
public void whenGetAllBooks_thenOK() {
    Response response = RestAssured.get(API_ROOT);
 
    assertEquals(HttpStatus.OK.value(), response.getStatusCode());
}

@Test
public void whenGetBooksByTitle_thenOK() {
    Book book = createRandomBook();
    createBookAsUri(book);
    Response response = RestAssured.get(
      API_ROOT + "/title/" + book.getTitle());
    
    assertEquals(HttpStatus.OK.value(), response.getStatusCode());
    assertTrue(response.as(List.class)
      .size() > 0);
}
@Test
public void whenGetCreatedBookById_thenOK() {
    Book book = createRandomBook();
    String location = createBookAsUri(book);
    Response response = RestAssured.get(location);
    
    assertEquals(HttpStatus.OK.value(), response.getStatusCode());
    assertEquals(book.getTitle(), response.jsonPath()
      .get("title"));
}

@Test
public void whenGetNotExistBookById_thenNotFound() {
    Response response = RestAssured.get(API_ROOT + "/" + randomNumeric(4));
    
    assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode());
}
Copy

Next, we'll test creating a new book:

@Test
public void whenCreateNewBook_thenCreated() {
    Book book = createRandomBook();
    Response response = RestAssured.given()
      .contentType(MediaType.APPLICATION_JSON_VALUE)
      .body(book)
      .post(API_ROOT);
    
    assertEquals(HttpStatus.CREATED.value(), response.getStatusCode());
}

@Test
public void whenInvalidBook_thenError() {
    Book book = createRandomBook();
    book.setAuthor(null);
    Response response = RestAssured.given()
      .contentType(MediaType.APPLICATION_JSON_VALUE)
      .body(book)
      .post(API_ROOT);
    
    assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusCode());
}
Copy

Then we'll update an existing book:



@Test
public void whenUpdateCreatedBook_thenUpdated() {
    Book book = createRandomBook();
    String location = createBookAsUri(book);
    book.setId(Long.parseLong(location.split("api/books/")[1]));
    book.setAuthor("newAuthor");
    Response response = RestAssured.given()
      .contentType(MediaType.APPLICATION_JSON_VALUE)
      .body(book)
      .put(location);
    
    assertEquals(HttpStatus.OK.value(), response.getStatusCode());

    response = RestAssured.get(location);
    
    assertEquals(HttpStatus.OK.value(), response.getStatusCode());
    assertEquals("newAuthor", response.jsonPath()
      .get("author"));
}
Copy

And we can delete a book:

@Test
public void whenDeleteCreatedBook_thenOk() {
    Book book = createRandomBook();
    String location = createBookAsUri(book);
    Response response = RestAssured.delete(location);
    
    assertEquals(HttpStatus.OK.value(), response.getStatusCode());

    response = RestAssured.get(location);
    assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode());
}
Copy


10. CONCLUSION



This was a quick but comprehensive introduction to Spring Boot.

Of course, we barely scratched the surface here. There's a lot more to this
framework than we can cover in a single intro article.

That's exactly why we have more than just a single article covering Boot on the
site.

As always, the full source code of our examples here is over on GitHub.

Generic bottom


GET STARTED WITH SPRING 5 AND SPRING BOOT 2, THROUGH THE LEARN SPRING COURSE:

>> CHECK OUT THE COURSE
Generic footer banner
Learning to build your API
with Spring?
Download the E-book
Comments are closed on this article!

res – REST with Spring (eBook) (everywhere)
Building a REST API with Spring 5?
Download the E-book





COURSES

 * All Courses
 * All Bulk Courses
 * All Bulk Team Courses
 * The Courses Platform

SERIES

 * Java “Back to Basics” Tutorial
 * Jackson JSON Tutorial
 * Apache HttpClient Tutorial
 * REST with Spring Tutorial
 * Spring Persistence Tutorial
 * Security with Spring
 * Spring Reactive Tutorials

ABOUT

 * About Baeldung
 * The Full Archive
 * Editors
 * Jobs
 * Our Partners
 * Partner with Baeldung

 * Terms of Service
 * Privacy Policy
 * Company Info
 * Contact