Home > Software design >  Spring Boot Web throws 404 with API call, correct project structure
Spring Boot Web throws 404 with API call, correct project structure

Time:03-17

SOLVED: Adding @Service annotation in CRUDServices.java and removing @ComponentScan in the main application solved EVERYTHING.

I have a small application with Spring Boot Web, JPA and PostgreSQL database. When trying to call ANY of these API calls I get a 404. In another application done the same way it's successfull. All classes are in the same package and I don't get why it wouldn't find the controller (which seems to be the issue for most others with similair/same problem).

It runs on port 8081 because I have another service running on port 8080.

I tried removing the ComponentScan but then get a missing bean exception. I don't get it - they are all in the same package structure? I only have ONE package and that's booking.crudservice

Main application

package booking.crudservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Service;

@SpringBootApplication
@ComponentScan(basePackages="booking.crudservice.CRUDservices")
public class CrudserviceApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(CrudserviceApplication.class, args);
    }
}

Controller

package booking.crudservice;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.bind.annotation.*;

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

/*
    This has PUT,POST, GET, DELETE requstest to the API endpoints for the CRUD operations
    in CRUDservices.java
 */

@RestController
@RequestMapping("/bookings")
public class BookingController
{
    @Autowired
    private CRUDServices services;

    //Return a specific booking by ID.
    @RequestMapping("/{bookingID}")
    public Optional<BookingEntity> getBookingByID(@PathVariable String bookingID)
    {
        return services.getBookingByID(bookingID);
    }

    //Return all bookings
    @RequestMapping("/allbookings")
    public List<BookingEntity> getAllBookings()
    {
        return services.getAllBookings();
    }

    @RequestMapping(method = RequestMethod.POST, value ="/addbooking")
    public void addBooking(@RequestBody BookingEntity booking)
    {
        services.addBooking(booking);
    }

    //Update a given booking (with a bookingID), and replace it with new BookingEntity instance.
    @RequestMapping(method = RequestMethod.PUT, value ="/bookings/{bookingID}")
    public void updateBooking(@RequestBody BookingEntity booking, @PathVariable String bookingID)
    {
        services.updateBooking(booking, bookingID);
    }

    //Deletes a booking with the given bookingID
    @RequestMapping(method = RequestMethod.DELETE, value ="/bookings/{bookingID}")
    public void deleteBooking(@PathVariable String bookingID)
    {
        services.deleteBooking(bookingID);
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>booking</groupId>
    <artifactId>crudservice</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>crudservice</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <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-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

application.properties file

server.port=8081
spring.datasource.url=jdbc:postgresql://localhost:5432/bookingDB
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL81Dialect

CRUDServices.java

package booking.crudservice;

import org.springframework.beans.factory.annotation.Autowired;

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

/*
    This service holds all CRUD operations performed on the database.
    This utilizes the BookingRepository.java, which extends the CRUDrepository interface.
    Using ORM via JPA all operations on the database can be easily performed here.
    To connect to the web GET,PUT,POST and DELETE http requests will be made on all the operations via
    BookingController, enabling the user and other possible services to interact with the database
    using API endpoints.
 */

public class CRUDServices
{
    @Autowired
    private BookingRepository repository;

    //READ all bookings from DB using GET request
    public List<BookingEntity> getAllBookings()
    {
        List<BookingEntity> bookings = new ArrayList<>();
        repository.findAll().
                forEach(bookings::add);

        return bookings;
    }
    //SEARCH for a booking, given bookingID
    public Optional<BookingEntity> getBookingByID(String bookingID)
    {
        return repository.findById(bookingID);
    }

    public void addBooking(BookingEntity booking)
    {
        repository.save(booking);
    }

    public void updateBooking(BookingEntity booking, String bookingID)
    {
        //1. Search for the given ID in the database
        //2. Replace the booking object on that ID with new booking object from parameter.
    }

    public void deleteBooking(String bookingID)
    {
        repository.deleteById(bookingID);
    }
}

CodePudding user response:

Since your CrudserviceApplication.class is present in the base package itself . You don't have to use the @ComponentScan annotation.

You should be having @Service annotation in your service class (CRUDServices.class) for spring to maintain and inject it.

Also, can you verify that you're calling all your handler methods with /bookings/yourhandlerendpoint prefix. In your case

  • /bookings/{bookingID}
  • /bookings/allbookings
  • /bookings/addbooking
  • /bookings/bookings/{bookingID}
  • /bookings/bookings/{bookingID}

Also, I could see that you've adding /bookings perfix twice for couple of methods.

CodePudding user response:

In CrudserviceApplication class you mentioned base-package as

@ComponentScan(basePackages="booking.crudservice.CRUDservices")

That means spring boot will look for classes annotated with spring stereotype annotations like

  • @Component
  • @Configuration
  • @Controller
  • @RestController
  • @Service

for classes present inside booking.crudservice.CRUDservices package and its sub-packages. However, I can see your BookingController class is present in booking.crudservice package which is not a sub-package (in fact, it's parent package).

That's why spring boot is not scanning BooKingController class and hance you're getting 404.

As a fix, you can remove basePackages parameter inside @ComponentScan annotation and it'll scan all components present inside booking.crudservice and its sub-package(s).

  • Related