I'm building a Todo application in Spring Boot with Spring Data JDBC. I've built out all the REST endpoints and they work fine in Unit Tests, but when I run the application to access the endpoints/pages in browser, I get the following error:
Parameter 0 of constructor in dev.iosenberg.todo.controllers.TodoController required a bean of type 'dev.iosenberg.todo.repositories.TodoRepository' that could not be found.
Here are some relevant files:
TodoApplication.java:
package dev.iosenberg.todo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TodoApplication {
public static void main(String[] args) {
SpringApplication.run(TodoApplication.class, args);
}
}
models/Todo.java:
package dev.iosenberg.todo.models;
import org.springframework.data.annotation.Id;
public record Todo(@Id Long id, Long userId, String name, String description, boolean completed) {
}
repositories/TodoRepository.java:
package dev.iosenberg.todo.repositories;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import dev.iosenberg.todo.models.Todo;
@Repository
public interface TodoRepository extends CrudRepository<Todo,Long>, PagingAndSortingRepository<Todo, Long>{
}
controllers/TodoController.java:
package dev.iosenberg.todo.controllers;
import java.net.URI;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.web.SpringDataWebProperties.Pageable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
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.RestController;
import org.springframework.web.util.UriComponentsBuilder;
import dev.iosenberg.todo.models.Todo;
import dev.iosenberg.todo.repositories.TodoRepository;
import java.util.List;
@RestController
@RequestMapping("/todos")
public class TodoController {
@Autowired
private TodoRepository todoRepository;
public TodoController(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}
@GetMapping
public ResponseEntity<List<Todo>> findAll(Pageable pageable) {
Page<Todo> page = todoRepository.findAll(
PageRequest.of(1,1)
);
return ResponseEntity.ok(page.getContent());
}
@GetMapping("/{requestedId}")
public ResponseEntity<Todo> findById(@PathVariable Long requestedId) {
Optional<Todo> todoOptional = todoRepository.findById(requestedId);
if(todoOptional.isPresent()) {
return ResponseEntity.ok(todoOptional.get());
}
else {
return ResponseEntity.notFound().build();
}
}
@PostMapping
public ResponseEntity<Void> createTodo(@RequestBody Todo newTodoRequest, UriComponentsBuilder ucb) {
Todo savedTodo = todoRepository.save(newTodoRequest);
URI locationOfNewTodo = ucb
.path("todos/{id}")
.buildAndExpand(savedTodo.id())
.toUri();
return ResponseEntity.created(locationOfNewTodo).build();
}
@PutMapping("/{id}")
private ResponseEntity<Void> putTodo(@PathVariable Long id, @RequestBody Todo todoUpdate) {
Optional<Todo> todoOptional = todoRepository.findById(id);
if(todoOptional.isPresent()) {
Todo updatedTodo = todoUpdate;
todoRepository.save(updatedTodo);
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
@DeleteMapping("/{id}")
private ResponseEntity<Void> deleteTodo(@PathVariable Long id) {
if(!todoRepository.existsById(id)) {
return ResponseEntity.notFound().build();
}
todoRepository.deleteById(id);
return ResponseEntity.noContent().build();
}
}
I also have this to serve some webpages MvcConfig.java:
package dev.iosenberg.todo;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/test").setViewName("test");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
}
}
build.gradle:
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.5'
id 'io.spring.dependency-management' version '1.1.3'
}
group = 'dev.iosenberg'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
repositories {
mavenCentral()
}
dependencies {
// implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
// implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
implementation 'org.springframework.data:spring-data-jdbc'
testImplementation 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// testImplementation 'org.springframework.security:spring-security-test'
}
tasks.named('test') {
useJUnitPlatform()
}
test {
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
showExceptions true
exceptionFormat "full"
showCauses true
showStackTraces true
// Change to `true` for more verbose test output
showStandardStreams = true
}
}
And here's the whole console output:
2023-11-09T12:09:23.449-05:00 INFO 27748 --- [ main] dev.iosenberg.todo.TodoApplication : Starting TodoApplication using Java 17.0.9 with PID 27748 (C:\Users\ikeos\Documents\git\todo\bin\main started by ikeos in C:\Users\ikeos\Documents\git\todo)
2023-11-09T12:09:23.455-05:00 INFO 27748 --- [ main] dev.iosenberg.todo.TodoApplication : No active profile set, falling back to 1 default profile: "default"
2023-11-09T12:09:25.106-05:00 INFO 27748 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2023-11-09T12:09:25.119-05:00 INFO 27748 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-11-09T12:09:25.120-05:00 INFO 27748 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.15]
2023-11-09T12:09:25.274-05:00 INFO 27748 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-11-09T12:09:25.277-05:00 INFO 27748 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1740 ms
2023-11-09T12:09:25.366-05:00 WARN 27748 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'todoController' defined in file [C:\Users\ikeos\Documents\git\todo\bin\main\dev\iosenberg\todo\controllers\TodoController.class]: Unsatisfied dependency expressed through constructor parameter 0: No qualifying bean of type 'dev.iosenberg.todo.repositories.TodoRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
2023-11-09T12:09:25.372-05:00 INFO 27748 --- [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2023-11-09T12:09:25.395-05:00 INFO 27748 --- [ main] .s.b.a.l.ConditionEvaluationReportLogger :
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2023-11-09T12:09:25.431-05:00 ERROR 27748 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in dev.iosenberg.todo.controllers.TodoController required a bean of type 'dev.iosenberg.todo.repositories.TodoRepository' that could not be found.
Action:
Consider defining a bean of type 'dev.iosenberg.todo.repositories.TodoRepository' in your configuration.
There's more code that's less relevant, but the whole repo is here if it's helpful: https://github.com/iosenberg/todo
I tried two solutions.
First was to add @ComponentScan and (basePackages = "dev.iosenberg.todo")/(basePackages = "dev.iosenberg.todo.repositories","dev.iosenberg.todo.controllers",etc.), and the best outcome was the removal of the Repository Bean error, but when I tried to access any pages served through the application, I get a Whitelabel 404 error page.
The second solution was to move my test repository (data.sql and schema.sql) from src/test/resources to src/main/resources, but I simply got SQL errors saying it didn't recognize the table.
I've scoured pretty much every Stack Overflow page mentioning the Repository error and have come up completely blank on what to do next.
The problem is you don't have a database.
You have declared
com.h2database:h2
as atestImplementation
dependency only, so it is available in the tests but not when you actually run the application.The easiest fix is to change that to an
implementation
dependency and your application will start up fine. Of course, for production you probably want to connect to a proper persistent database. Also you'll want to use Testcontainers instead of an in-memory database for integration tests. But these are beyond the scope of this question.How to debug problems with Spring Boots autoconfiguration
Enable Spring Boot's debugging by adding
debug=true
to yourapplication.properties
fileLook especially in the section
Negative matches
of the output for auto-configurations that should happen but don't. Searching for relevant technologies is a good idea. Searching for JDBC yields the following results that seem relevant:This looks like you have a
DataSource
, which just demonstrates that you shouldn't stop at the first match.The next match is:
So,
DataSourceAutoConfiguration
did run, but we still don't seem to have aDataSource
bean. From this we learn that there is an important difference between a class being available and a bean of that type being available. Kind of obvious when you say it out loud, yet easy to miss when you look at some log file.The rest of the search turns up just more stuff that doesn't work, which is either irrelevant, or not surprising if there is no
DataSource
bean. Therefore I switched to searching for datasource in the log.That returns tons of hits. But the first one in the
Negative matches
was really helpful.So no embedded database found! What database is it trying to use? I checked the
application.properties
for a jdbc url (there is none) and your dependencies for databases and only found the above mentioned test dependency.