Search code examples
spring-bootrestcrud-repository

How to us a constructor with parameters in a method used by Spring Boot's @RestController annotation to create a request handler


I bought this new book to try to learn Spring Boot quickly. It started out well, and I easily created a REST API. But then we added CrudRepository, and I'm seeing issues with the code as described in the book. Also, there is no code available to download because the author took it down from Oreily's git repo in order to fix some things...

The issue is that if I try to build the code as the book describes (without a default constructor) I get a Java error complaining that there is no default constructor. If I add a default constructor, it builds, but Spring uses it instead of the new constructor, that requires a parameter to be passed. So when I actually call the API, like if I call the /coffees endpoint, I get a java.lang.NullPointerException: null

So how is Spring supposed to know which constructor to use, and how could it pass in values for this parameter?

Here is the controller:

package com.bw.restdemo;

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

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/coffees")
class RestAPIDemoController {
    private final CoffeeRepository coffeeRepository;
    
    public RestAPIDemoController(CoffeeRepository coffeeRepository) {
        this.coffeeRepository = coffeeRepository;
        
        this.coffeeRepository.saveAll(List.of(
                new Coffee("Cafe Cereza"),
                new Coffee("Freedom Fuel"),
                new Coffee("Cold Brew"),
                new Coffee("Sumatra")
                ));
    }
    public RestAPIDemoController() {
        this.coffeeRepository = null; 
    }; 
    
    //@RequestMapping(value = "/coffees", method = RequestMethod.GET)
    @GetMapping
    Iterable<Coffee> getCoffees() {
        return coffeeRepository.findAll();
    }
    
    @GetMapping("/{id}")
    Optional<Coffee> getCoffeeById(@PathVariable String id) {
        return coffeeRepository.findById(id); 
    }
    
    @PostMapping
    Coffee postCoffee(@RequestBody Coffee coffee) {
        return coffeeRepository.save(coffee); 
    }
    
    @PutMapping("/{id}")
    ResponseEntity<Coffee> putCoffee(@PathVariable String id, @RequestBody Coffee coffee) {
        return (!coffeeRepository.existsById(id)) 
                ? new ResponseEntity<>(coffeeRepository.save(coffee), HttpStatus.CREATED) 
                : new ResponseEntity<>(coffeeRepository.save(coffee), HttpStatus.OK);
    }
    
    @DeleteMapping("/{id}")
    void deleteCoffee(@PathVariable String id) {
        coffeeRepository.deleteById(id); 
    }
}

Here is where I'm defining the interface:

package com.bw.restdemo;

import org.springframework.data.repository.CrudRepository;

interface CoffeeRepository extends CrudRepository<Coffee, String> {
    
}

And here's the main class -- apologies for the class stuffed at the bottom.

package com.bw.restdemo;

import java.util.UUID;

import javax.persistence.Entity;
import javax.persistence.Id;

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

@SpringBootApplication
public class RestDemoApplication {

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

}

@Entity
class Coffee {
    @Id
    private String id;
    private String name;
    
    public Coffee(String id, String name) {
        this.id = id;
        this.name = name;
    }
    public void setId(String id) {
        this.id = id; 
    }
    
    public Coffee(String name) {
        this(UUID.randomUUID().toString(), name); 
    }
    public String getId() {
        return id; 
    }
    public String getName() {
        return name; 
    }
    public void setName(String name) {
        this.name = name; 
    }
}

Solution

  • CoffeeRepository interface is missing @Repository Annotation.

    Update:

    • Add @Repository Annotation at CoffeeRepository
    • Remove the default constructor from RestAPIDemoController.
    package com.bw.restdemo;
    
    import org.springframework.data.repository.CrudRepository;
    import org.springframework.stereotype.Repository;
    
    @Repository
    interface CoffeeRepository extends CrudRepository<Coffee, String> {
        
    }
    

    Explanation

    In spring framework, @Component annotation marks a java class as a bean so the component-scanning mechanism can pick it up and pull it into the application context. As @Repository serves as a specialization of @Component , it also enable annotated classes to be discovered and registered with application context.

    More at HowToDoInJava - @Repository annotation in Spring Boot