Thursday, February 9, 2023

How to Fix Spring Boot cannot access REST Controller on localhost (404) Error in Java

Hello guys, if you are getting "cannot access REST Controller on localhost (404)" error in your Spring Boot application and wondering how to solve it then you have come to the right place. Earlier, I have shared how to solved 5 common Spring Framework errors and exception and in this article, we will take a look at the spring boot can’t access rest controller on localhost (404) issue. A 404 Not Found Error for your REST Controllers is a relatively typical problem that many developers encounter while working with Spring Boot applications. In this Spring boot tutorial, let's explore why such an error took place, and later on, we will learn how to fix this issue.


Problem Description

Let’s understand it with the help of a scenario. We have a REST API whose objective is to serve as the cricket match information provider. At this point we have only one endpoint /team-info, as shown in the example below:


REST Controller

package com.javarevisited.ipldashboard.controller; 
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/v1") public class MatchController{ @RequestMapping("/team-info") public ResponseEntity<Team> getTeamInfo(){ // I hardcode the team India info here Team team = new Team(); team.setId(1); team.setTeamName("India"); team.setTotalMatches(30); team.setTotalWins(21); return new ResponseEntity<>(team, HttpStatus.OK); } }


Now, we need something to run this application. You can use the following Application’s main class to launch your Spring Boot application.


Spring Boot App (Main application)


package com.javarevisited.ipldashboard.app; 
import
org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class IplDashboardApplication { public static void main(String[] args) { SpringApplication.run(IplDashboardApplication.class, args); } }


Team Class (Model)


package com.javarevisited.ipldashboard.model; 
import
jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import lombok.Data; @Entity @Data public class Team { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String teamName; private long totalMatches; private long totalWins; public Team(String teamName, long totalMatches){ this.teamName = teamName; this.totalMatches = totalMatches; } public Team() { } @Override public String toString(){ return this.teamName + " " + this.totalMatches + " " + this.totalWins; } }


The team model class in the above-mentioned code stores team statistics like the number of games and victories.


Running the application

Now it’s time to run our application and access the exposed end-point i.e. /team-info. Unfortunately, when you try to access the "/team-info " endpoint it will encounter a 404 error:

{

    "timestamp": "2023-01-03T12:38:22.853+00:00",

    "status": 404,

    "error": "Not Found",

    "message": "No message available",

    "path": "/api/v1/team-info"

}



How to solve this problem?

First of all, you need to know where to look for possible issues. Here we need to concentrate on two classes (i.e. IplDashboardApplication and MatchController) 

We start with an annotation is written on top of class IplDashboardApplication i.e. @SpringBootApplication annotation to comprehend the problem. In actuality, this annotation is a synthesis of three annotations: 

@Configuration

@ComponentScan

@EnableAutoConfiguration

Without any additional inputs, the default @ComponentScan annotation instructs Spring to scan the current package and all of its child packages.

Remember: Our main application resides in package (com.javarevisited.ipldashboard.app) whereas the controller is placed under (com.javarevisited.ipldashboard.controller) the package.

The classes in the package com.javarevisited.ipldashboard.app and under it will thus be the only ones scanned. On the other hand, our controller class MatchController resides in a different package com.javarevisited.ipldashboard.controller which is neither in the application’s package nor in its sub package. 

We understood the problem that, the application is unable to find the REST controller. Therefore it throws can’t access the REST Controller on localhost (404). Now it’s time to solve the problem. There are possible two ways to fix this issue.


1. Place the Application under the root folder

The Main Application class IplDashboardApplication should be positioned in the project's root folder, as this is an excellent practice. In this manner, it will automatically search for the Controller class that is included in a sub-package. 

Here is an effective package structure as illustrated below:





As you can see from the above image we have taken out the IplDashboardApplication class and placed it at the root package com.javarevisited.ipldashboard, hence it can easily find the classes from other packages. The updated code script can be seen below.


Spring Boot App (Main application)


package com.javarevisited.ipldashboard; // App is in the root package



import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;





@SpringBootApplication

public class IplDashboardApplication {



   public static void main(String[] args) {

      SpringApplication.run(IplDashboardApplication.class, args);

   }



}



REST Controller

package com.javarevisited.ipldashboard.controller; 



import com.javarevisited.ipldashboard.model.Team;

import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;



@RestController

@RequestMapping("/api/v1")

public class MatchController{



    @RequestMapping("/team-info")

    public ResponseEntity<Team> getTeamInfo(){



        Team team = new Team();

        team.setId(1);

        team.setTeamName("India");

        team.setTotalMatches(30);

        team.setTotalWins(21);



        return new ResponseEntity<>(team, HttpStatus.OK);

    }

}


GET request to /api/v1/team-info will return following information.

{

    "id": 1,

    "teamName": "India",

    "totalMatches": 30,

    "totalWins": 21

}


Team Class (Model)


package com.javarevisited.ipldashboard.model;



import jakarta.persistence.Entity;

import jakarta.persistence.GeneratedValue;

import jakarta.persistence.GenerationType;

import jakarta.persistence.Id;

import lombok.Data;



@Entity

@Data

public class Team {



    @Id

    @GeneratedValue(strategy = GenerationType.AUTO)

    private long id;

    private String teamName;

    private long totalMatches;

    private long totalWins;



    public Team(String teamName, long totalMatches){

        this.teamName = teamName;

        this.totalMatches = totalMatches;

    }



    public Team() {



    }



    @Override

    public String toString(){

        return this.teamName + " " + this.totalMatches + " " + this.totalWins;

    }

}


2. Add Explicit Scan (@ComponentScan)

Another choice is to include the @ComponentScan annotation and tell it explicitly the packages to scan as a beginning point for the application.


@ComponentScan(basePackages = "com.javarevisited.ipldashboard.controller")


By adding the above annotation we can tell our application to scan the classes residing inside the com.javarevisited.ipldashboard.controller package. By doing this our application will be able to read MatchController thus, this also resolves the issue. Below is the updated code script.


Spring Boot App (Main application)


import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.context.annotation.ComponentScan;



@SpringBootApplication

@ComponentScan(basePackages = "com.javarevisited.ipldashboard.controller")

public class IplDashboardApplication {



   public static void main(String[] args) {

      SpringApplication.run(IplDashboardApplication.class, args);

   }



}



REST Controller

package com.javarevisited.ipldashboard.controller;



import com.javarevisited.ipldashboard.model.Team;

import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;



@RestController

@RequestMapping("/api/v1")

public class MatchController{



    @RequestMapping("/team-info")

    public ResponseEntity<Team> getTeamInfo(){



        Team team = new Team();

        team.setId(1);

        team.setTeamName("India");

        team.setTotalMatches(30);

        team.setTotalWins(21);



        return new ResponseEntity<>(team, HttpStatus.OK);

    }

}


Team Class (Model)

package com.javarevisited.ipldashboard.model;



import jakarta.persistence.Entity;

import jakarta.persistence.GeneratedValue;

import jakarta.persistence.GenerationType;

import jakarta.persistence.Id;

import lombok.Data;



@Entity

@Data

public class Team {



    @Id

    @GeneratedValue(strategy = GenerationType.AUTO)

    private long id;

    private String teamName;

    private long totalMatches;

    private long totalWins;



    public Team(String teamName, long totalMatches){

        this.teamName = teamName;

        this.totalMatches = totalMatches;

    }



    public Team() {



    }



    @Override

    public String toString(){

        return this.teamName + " " + this.totalMatches + " " + this.totalWins;

    }

}


Now if you make a GET request to /api/v1/team-info you will get the team’s information as seen below.


{

    "id": 1,

    "teamName": "India",

    "totalMatches": 30,

    "totalWins": 21

}



Conclusion

In this article, we learn how to fix a common problem that most developer encounter while working with Spring Boot application and that is Spring Boot can’t find REST Controller on localhost (404). We started our discussion with a simple scenario to understand why such a problem took place at first and then we dig deep into its two possible solutions. 

The first one is placing your spring boot application under the root package so that it will scan for components in packages below com.javarevisited.ipldashboard and the second one is to scan the controller package explicitly by adding @ComponentScan annotation. I hope the discussed solution helps you to fix the problem.  


5 comments:

Feel free to comment, ask questions if you have any doubt.