Building REST APIs with Spring Boot

Building robust and scalable REST APIs is a critical skill in modern software development. Spring Boot, part of the Spring Framework, is a powerful framework that simplifies the process of building these APIs. With its ease of use, powerful features, and large ecosystem, Spring Boot has become a popular choice for developers building production-grade applications.

In this article, we will cover how to build RESTful APIs using Spring Boot. We will dive into practical code examples, analyze the performance, and explore best practices to ensure you build efficient and maintainable APIs.

What is REST?

REST (Representational State Transfer) is an architectural style that dictates how web services should be built and interact. RESTful services are stateless, rely on standard HTTP methods, and expose resources through a well-defined URI structure. Here are the key concepts:

  • Resources: Represented using URIs.
  • Statelessness: Each API call contains all the necessary information to fulfill the request.
  • HTTP Methods: Common methods include GET, POST, PUT, DELETE, PATCH, and OPTIONS.

Why Spring Boot for REST APIs?

Spring Boot streamlines the development process of RESTful services with the following features:

  • Auto-configuration: Eliminates the need for excessive XML configuration.
  • Embedded server: By default, Spring Boot comes with embedded servers like Tomcat, which makes it easier to deploy standalone applications.
  • Security: Provides easy-to-implement authentication and authorization mechanisms with Spring Security.
  • Actuator: Monitoring and checking the health of endpoints for production environments.
  • JPA Integration: Simplifies database operations with Spring Data JPA.

Setting Up a Spring Boot Project

You can set up a Spring Boot project using Spring Initializr (https://start.spring.io/) or directly from your IDE.

Maven Dependencies

Here is an example of the pom.xml for a Spring Boot REST API:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<dependencies>
<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>
<scope>runtime</scope>
</dependency>
</dependencies>
<dependencies> <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> <scope>runtime</scope> </dependency> </dependencies>
<dependencies>
    <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>
        <scope>runtime</scope>
    </dependency>
</dependencies>

Main Application Class

The entry point for the Spring Boot application is the @SpringBootApplication class, which enables auto-configuration and component scanning.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@SpringBootApplication
public class RestApiApplication {
public static void main(String[] args) {
SpringApplication.run(RestApiApplication.class, args);
}
}
@SpringBootApplication public class RestApiApplication { public static void main(String[] args) { SpringApplication.run(RestApiApplication.class, args); } }
@SpringBootApplication
public class RestApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(RestApiApplication.class, args);
    }
}

Creating RESTful Endpoints

Let’s create a simple REST API that manages a collection of books. We’ll follow standard RESTful practices by using HTTP methods to create, retrieve, update, and delete resources.

Model

Create a Book entity representing the data model:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String author;
// Getters and Setters
}
@Entity public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title; private String author; // Getters and Setters }
@Entity
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String author;

    // Getters and Setters
}

Repository

Spring Data JPA simplifies the data access layer. By extending the JpaRepository, you get basic CRUD operations for free.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public interface BookRepository extends JpaRepository<Book, Long> {
}
public interface BookRepository extends JpaRepository<Book, Long> { }
public interface BookRepository extends JpaRepository<Book, Long> {
}

Controller

The BookController will handle API requests. It uses annotations like @RestController and @RequestMapping to map endpoints.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@RestController
@RequestMapping("/api/books")
public class BookController {
@Autowired
private BookRepository bookRepository;
@GetMapping
public List<Book> getAllBooks() {
return bookRepository.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<Book> getBookById(@PathVariable Long id) {
return bookRepository.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public Book createBook(@RequestBody Book book) {
return bookRepository.save(book);
}
@PutMapping("/{id}")
public ResponseEntity<Book> updateBook(@PathVariable Long id, @RequestBody Book updatedBook) {
return bookRepository.findById(id).map(book -> {
book.setTitle(updatedBook.getTitle());
book.setAuthor(updatedBook.getAuthor());
return ResponseEntity.ok(bookRepository.save(book));
}).orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteBook(@PathVariable Long id) {
return bookRepository.findById(id).map(book -> {
bookRepository.delete(book);
return ResponseEntity.ok().build();
}).orElse(ResponseEntity.notFound().build());
}
}
@RestController @RequestMapping("/api/books") public class BookController { @Autowired private BookRepository bookRepository; @GetMapping public List<Book> getAllBooks() { return bookRepository.findAll(); } @GetMapping("/{id}") public ResponseEntity<Book> getBookById(@PathVariable Long id) { return bookRepository.findById(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @PostMapping public Book createBook(@RequestBody Book book) { return bookRepository.save(book); } @PutMapping("/{id}") public ResponseEntity<Book> updateBook(@PathVariable Long id, @RequestBody Book updatedBook) { return bookRepository.findById(id).map(book -> { book.setTitle(updatedBook.getTitle()); book.setAuthor(updatedBook.getAuthor()); return ResponseEntity.ok(bookRepository.save(book)); }).orElse(ResponseEntity.notFound().build()); } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteBook(@PathVariable Long id) { return bookRepository.findById(id).map(book -> { bookRepository.delete(book); return ResponseEntity.ok().build(); }).orElse(ResponseEntity.notFound().build()); } }
@RestController
@RequestMapping("/api/books")
public class BookController {

    @Autowired
    private BookRepository bookRepository;

    @GetMapping
    public List<Book> getAllBooks() {
        return bookRepository.findAll();
    }

    @GetMapping("/{id}")
    public ResponseEntity<Book> getBookById(@PathVariable Long id) {
        return bookRepository.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    public Book createBook(@RequestBody Book book) {
        return bookRepository.save(book);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Book> updateBook(@PathVariable Long id, @RequestBody Book updatedBook) {
        return bookRepository.findById(id).map(book -> {
            book.setTitle(updatedBook.getTitle());
            book.setAuthor(updatedBook.getAuthor());
            return ResponseEntity.ok(bookRepository.save(book));
        }).orElse(ResponseEntity.notFound().build());
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteBook(@PathVariable Long id) {
        return bookRepository.findById(id).map(book -> {
            bookRepository.delete(book);
            return ResponseEntity.ok().build();
        }).orElse(ResponseEntity.notFound().build());
    }
}

Testing the API with Postman or Curl

You can use tools like Postman or curl to test the API. Below are examples of how to test the API using curl commands:

  • GET all books:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
curl -X GET http://localhost:8080/api/books
curl -X GET http://localhost:8080/api/books
curl -X GET http://localhost:8080/api/books
  • POST a new book:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
curl -X POST http://localhost:8080/api/books -H 'Content-Type: application/json' -d '{"title":"1984", "author":"George Orwell"}'
curl -X POST http://localhost:8080/api/books -H 'Content-Type: application/json' -d '{"title":"1984", "author":"George Orwell"}'
curl -X POST http://localhost:8080/api/books -H 'Content-Type: application/json' -d '{"title":"1984", "author":"George Orwell"}'
  • PUT to update a book:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
curl -X PUT http://localhost:8080/api/books/1 -H 'Content-Type: application/json' -d '{"title":"Animal Farm", "author":"George Orwell"}'
curl -X PUT http://localhost:8080/api/books/1 -H 'Content-Type: application/json' -d '{"title":"Animal Farm", "author":"George Orwell"}'
curl -X PUT http://localhost:8080/api/books/1 -H 'Content-Type: application/json' -d '{"title":"Animal Farm", "author":"George Orwell"}'
  • DELETE a book:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
curl -X DELETE http://localhost:8080/api/books/1
curl -X DELETE http://localhost:8080/api/books/1
curl -X DELETE http://localhost:8080/api/books/1

Performance Analysis

Database Interaction

Efficient database interaction is crucial for high-performance REST APIs. By default, Spring Data JPA generates SQL queries based on method signatures. Let’s analyze a few things that can help improve performance:

1. Lazy Loading vs Eager Loading: By default, relationships should be loaded lazily unless you know you’ll always need them. This reduces unnecessary database calls. For example:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@ManyToOne(fetch = FetchType.LAZY)
private Author author;
@ManyToOne(fetch = FetchType.LAZY) private Author author;
@ManyToOne(fetch = FetchType.LAZY)

private Author author;

2. Batch Inserts/Updates: If you’re dealing with bulk operations, make use of batching in JPA.

3. Caching: Use Spring’s caching mechanism or a second-level cache like Ehcache to improve performance by reducing database hits for frequently accessed data.

API Response Time

Spring Boot provides great built-in monitoring features through Spring Boot Actuator, allowing you to track response times and application health.

Here’s an example of enabling Actuator for monitoring:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

With Actuator, you can monitor endpoints like /actuator/health and /actuator/metrics to analyze the health of your API and performance metrics like response times and CPU utilization.

Testing API Performance

You can use tools like Apache JMeter or Gatling to perform load testing on your Spring Boot application. Here’s a quick look at typical performance metrics collected:

Metric Description Value
Response Time Time taken to get a response from the server 120ms (avg)
Throughput Number of requests per second 250 req/sec
CPU Usage CPU utilization during high load 70%
Memory Usage Amount of memory used by the application 300MB

Security Considerations

While building APIs, security is paramount. Spring Boot integrates with Spring Security to secure your endpoints with authentication and authorization.

Here’s how you can secure the API using Basic Authentication:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/books/**").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers(HttpMethod.GET, "/api/books/**").permitAll() .anyRequest().authenticated() .and() .httpBasic(); } }
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers(HttpMethod.GET, "/api/books/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .httpBasic();
    }
}

Conclusion

Spring Boot is a powerful framework for building RESTful APIs with minimal configuration. By leveraging its out-of-the-box features, you can quickly build scalable, maintainable, and secure APIs. With proper performance tuning, database optimization, and security practices, your Spring Boot-based REST APIs will be ready for production use.

Contact Us
Contact Us

    • United States+1
    • United Kingdom+44
    • Afghanistan (‫افغانستان‬‎)+93
    • Albania (Shqipëri)+355
    • Algeria (‫الجزائر‬‎)+213
    • American Samoa+1
    • Andorra+376
    • Angola+244
    • Anguilla+1
    • Antigua and Barbuda+1
    • Argentina+54
    • Armenia (Հայաստան)+374
    • Aruba+297
    • Ascension Island+247
    • Australia+61
    • Austria (Österreich)+43
    • Azerbaijan (Azərbaycan)+994
    • Bahamas+1
    • Bahrain (‫البحرين‬‎)+973
    • Bangladesh (বাংলাদেশ)+880
    • Barbados+1
    • Belarus (Беларусь)+375
    • Belgium (België)+32
    • Belize+501
    • Benin (Bénin)+229
    • Bermuda+1
    • Bhutan (འབྲུག)+975
    • Bolivia+591
    • Bosnia and Herzegovina (Босна и Херцеговина)+387
    • Botswana+267
    • Brazil (Brasil)+55
    • British Indian Ocean Territory+246
    • British Virgin Islands+1
    • Brunei+673
    • Bulgaria (България)+359
    • Burkina Faso+226
    • Burundi (Uburundi)+257
    • Cambodia (កម្ពុជា)+855
    • Cameroon (Cameroun)+237
    • Canada+1
    • Cape Verde (Kabu Verdi)+238
    • Caribbean Netherlands+599
    • Cayman Islands+1
    • Central African Republic (République centrafricaine)+236
    • Chad (Tchad)+235
    • Chile+56
    • China (中国)+86
    • Christmas Island+61
    • Cocos (Keeling) Islands+61
    • Colombia+57
    • Comoros (‫جزر القمر‬‎)+269
    • Congo (DRC) (Jamhuri ya Kidemokrasia ya Kongo)+243
    • Congo (Republic) (Congo-Brazzaville)+242
    • Cook Islands+682
    • Costa Rica+506
    • Côte d’Ivoire+225
    • Croatia (Hrvatska)+385
    • Cuba+53
    • Curaçao+599
    • Cyprus (Κύπρος)+357
    • Czech Republic (Česká republika)+420
    • Denmark (Danmark)+45
    • Djibouti+253
    • Dominica+1
    • Dominican Republic (República Dominicana)+1
    • Ecuador+593
    • Egypt (‫مصر‬‎)+20
    • El Salvador+503
    • Equatorial Guinea (Guinea Ecuatorial)+240
    • Eritrea+291
    • Estonia (Eesti)+372
    • Eswatini+268
    • Ethiopia+251
    • Falkland Islands (Islas Malvinas)+500
    • Faroe Islands (Føroyar)+298
    • Fiji+679
    • Finland (Suomi)+358
    • France+33
    • French Guiana (Guyane française)+594
    • French Polynesia (Polynésie française)+689
    • Gabon+241
    • Gambia+220
    • Georgia (საქართველო)+995
    • Germany (Deutschland)+49
    • Ghana (Gaana)+233
    • Gibraltar+350
    • Greece (Ελλάδα)+30
    • Greenland (Kalaallit Nunaat)+299
    • Grenada+1
    • Guadeloupe+590
    • Guam+1
    • Guatemala+502
    • Guernsey+44
    • Guinea (Guinée)+224
    • Guinea-Bissau (Guiné Bissau)+245
    • Guyana+592
    • Haiti+509
    • Honduras+504
    • Hong Kong (香港)+852
    • Hungary (Magyarország)+36
    • Iceland (Ísland)+354
    • India (भारत)+91
    • Indonesia+62
    • Iran (‫ایران‬‎)+98
    • Iraq (‫العراق‬‎)+964
    • Ireland+353
    • Isle of Man+44
    • Israel (‫ישראל‬‎)+972
    • Italy (Italia)+39
    • Jamaica+1
    • Japan (日本)+81
    • Jersey+44
    • Jordan (‫الأردن‬‎)+962
    • Kazakhstan (Казахстан)+7
    • Kenya+254
    • Kiribati+686
    • Kosovo+383
    • Kuwait (‫الكويت‬‎)+965
    • Kyrgyzstan (Кыргызстан)+996
    • Laos (ລາວ)+856
    • Latvia (Latvija)+371
    • Lebanon (‫لبنان‬‎)+961
    • Lesotho+266
    • Liberia+231
    • Libya (‫ليبيا‬‎)+218
    • Liechtenstein+423
    • Lithuania (Lietuva)+370
    • Luxembourg+352
    • Macau (澳門)+853
    • Macedonia (FYROM) (Македонија)+389
    • Madagascar (Madagasikara)+261
    • Malawi+265
    • Malaysia+60
    • Maldives+960
    • Mali+223
    • Malta+356
    • Marshall Islands+692
    • Martinique+596
    • Mauritania (‫موريتانيا‬‎)+222
    • Mauritius (Moris)+230
    • Mayotte+262
    • Mexico (México)+52
    • Micronesia+691
    • Moldova (Republica Moldova)+373
    • Monaco+377
    • Mongolia (Монгол)+976
    • Montenegro (Crna Gora)+382
    • Montserrat+1
    • Morocco (‫المغرب‬‎)+212
    • Mozambique (Moçambique)+258
    • Myanmar (Burma) (မြန်မာ)+95
    • Namibia (Namibië)+264
    • Nauru+674
    • Nepal (नेपाल)+977
    • Netherlands (Nederland)+31
    • New Caledonia (Nouvelle-Calédonie)+687
    • New Zealand+64
    • Nicaragua+505
    • Niger (Nijar)+227
    • Nigeria+234
    • Niue+683
    • Norfolk Island+672
    • North Korea (조선 민주주의 인민 공화국)+850
    • Northern Mariana Islands+1
    • Norway (Norge)+47
    • Oman (‫عُمان‬‎)+968
    • Pakistan (‫پاکستان‬‎)+92
    • Palau+680
    • Palestine (‫فلسطين‬‎)+970
    • Panama (Panamá)+507
    • Papua New Guinea+675
    • Paraguay+595
    • Peru (Perú)+51
    • Philippines+63
    • Poland (Polska)+48
    • Portugal+351
    • Puerto Rico+1
    • Qatar (‫قطر‬‎)+974
    • Réunion (La Réunion)+262
    • Romania (România)+40
    • Russia (Россия)+7
    • Rwanda+250
    • Saint Barthélemy+590
    • Saint Helena+290
    • Saint Kitts and Nevis+1
    • Saint Lucia+1
    • Saint Martin (Saint-Martin (partie française))+590
    • Saint Pierre and Miquelon (Saint-Pierre-et-Miquelon)+508
    • Saint Vincent and the Grenadines+1
    • Samoa+685
    • San Marino+378
    • São Tomé and Príncipe (São Tomé e Príncipe)+239
    • Saudi Arabia (‫المملكة العربية السعودية‬‎)+966
    • Senegal (Sénégal)+221
    • Serbia (Србија)+381
    • Seychelles+248
    • Sierra Leone+232
    • Singapore+65
    • Sint Maarten+1
    • Slovakia (Slovensko)+421
    • Slovenia (Slovenija)+386
    • Solomon Islands+677
    • Somalia (Soomaaliya)+252
    • South Africa+27
    • South Korea (대한민국)+82
    • South Sudan (‫جنوب السودان‬‎)+211
    • Spain (España)+34
    • Sri Lanka (ශ්‍රී ලංකාව)+94
    • Sudan (‫السودان‬‎)+249
    • Suriname+597
    • Svalbard and Jan Mayen+47
    • Sweden (Sverige)+46
    • Switzerland (Schweiz)+41
    • Syria (‫سوريا‬‎)+963
    • Taiwan (台灣)+886
    • Tajikistan+992
    • Tanzania+255
    • Thailand (ไทย)+66
    • Timor-Leste+670
    • Togo+228
    • Tokelau+690
    • Tonga+676
    • Trinidad and Tobago+1
    • Tunisia (‫تونس‬‎)+216
    • Turkey (Türkiye)+90
    • Turkmenistan+993
    • Turks and Caicos Islands+1
    • Tuvalu+688
    • U.S. Virgin Islands+1
    • Uganda+256
    • Ukraine (Україна)+380
    • United Arab Emirates (‫الإمارات العربية المتحدة‬‎)+971
    • United Kingdom+44
    • United States+1
    • Uruguay+598
    • Uzbekistan (Oʻzbekiston)+998
    • Vanuatu+678
    • Vatican City (Città del Vaticano)+39
    • Venezuela+58
    • Vietnam (Việt Nam)+84
    • Wallis and Futuna (Wallis-et-Futuna)+681
    • Western Sahara (‫الصحراء الغربية‬‎)+212
    • Yemen (‫اليمن‬‎)+967
    • Zambia+260
    • Zimbabwe+263
    • Åland Islands+358

    Insert math as
    Block
    Inline
    Additional settings
    Formula color
    Text color
    #333333
    Type math using LaTeX
    Preview
    Nothing to preview
    Insert