188

I'm trying to to mix mvc and rest in a single spring boot project.

I want to set base path for all rest controllers (eg. example.com/api) in a single place (I don't want annotate each controller with @RequestMapping('api/products'), instead, just @RequestMapping('/products').

Mvc controllers should be accessible by example.com/whatever

Is it possible?

(I don't use spring data rest, just spring mvc)

danronmoon
  • 3,814
  • 5
  • 34
  • 56
Teimuraz
  • 8,795
  • 5
  • 35
  • 62
  • take a look at this http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-pattern-comparison – leeor Oct 04 '15 at 03:02
  • server.servlet.contextPath=/api – Daniel T. Sobrosa Jan 22 '19 at 17:40
  • spring boot version 2.1.4.RELEASE, spring.mvc.servlet.path=/api and server.servlet.context-path=/api , both works – Pramod Kumar Sharma May 08 '19 at 20:01
  • server.servlet.context-path=/api solution is for APPLICATION , not for only REST. It is valid for SOAP services also. If you want to sperate your SOAP and REST services path, you should use @RequestMapping('api/...')... https://medium.com/@bm.celalkartal/how-to-create-rest-and-soap-services-in-same-spring-boot-application-9054d69767a6 – bmck May 30 '20 at 10:55

20 Answers20

163

With Spring Boot 1.2+ (<2.0) all it takes is a single property in application.properties:

spring.data.rest.basePath=/api

ref link : https://docs.spring.io/spring-data/rest/docs/current/reference/html/#getting-started.changing-base-uri

For 2.x, use

server.servlet.context-path=/api
kenny_k
  • 3,831
  • 5
  • 30
  • 41
Suroj
  • 2,127
  • 1
  • 12
  • 14
  • 4
    This is the exact answer that thorinkor gave. – Jean-François Beauchef Jul 07 '17 at 19:44
  • 8
    Thanks, but this does not work for me in Spring Boot version v1.5.7.RELEASE. The other answer server.contextPath=/api worked – Jay Dec 21 '17 at 11:05
  • 14
    @Suroj That solution works only with RepositoryRestController annotated controllers, not with RestController... – Nano Apr 20 '18 at 13:51
  • https://jira.spring.io/browse/DATAREST-1211 This Jira Ticket mentions it being "spring.data.rest.base-path for Spring Boot 2.0.0". Sadly, both don't work for me. – Carsten Hagemann May 16 '18 at 11:22
  • 6
    for SB 2+ it's server.servlet.context-path=/url – vicky Jan 15 '19 at 12:57
  • spring boot version 2.1.4.RELEASE, spring.mvc.servlet.path=/api and server.servlet.context-path=/api , both works – Pramod Kumar Sharma May 08 '19 at 20:02
  • I believe this answer is correct for the REST services automatically generated by Spring Data REST. This isn't the same thing as for REST services you write yourself using @RestController and Spring MVC. – Charlie Reitzel May 28 '19 at 21:57
  • 3
    I don't understand how this answer got accepted or got so many up-votes. As mentioned by several other comments, this answer does not work (for @RestController) and is equivalent to another answer given almost 6 months before. – Ekeko Sep 21 '19 at 20:15
  • 2
    `server.servlet.context-path=/api` sets the base path for all the requests. If you want to serve static files (HTML, CSS Javascript) from `/` while all REST API endpoints should be on `/api`, this won't work, because also the static HTML and other files will be served from `/api`. See other responses below! – Girts Strazdins Mar 05 '22 at 12:29
103

A bit late but the same question brought me here before reaching the answer so I post it here. Create (if you still don't have it) an application.properties and add

server.contextPath=/api

So in the previous example if you have a RestController with @RequestMapping("/test") you will access it like localhost:8080/api/test/{your_rest_method}

question source: how do i choose the url for my spring boot webapp

Community
  • 1
  • 1
OriolBG
  • 2,031
  • 2
  • 18
  • 21
61

For spring boot framework version 2.0.4.RELEASE+. Add this line to application.properties

server.servlet.context-path=/api
duyuanchao
  • 3,863
  • 1
  • 25
  • 16
37

Try using a PathMatchConfigurer (Spring Boot 2.x):

@Configuration
public class WebMvcConfig implements WebMvcConfigurer  {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("api", HandlerTypePredicate.forAnnotation(RestController.class));
    }
}
Harald Wendel
  • 379
  • 3
  • 2
  • 3
    Thanks, this was exactly what I was looking for! This allows you to set a context path element for all RestControllers configured through this WebMvcConfig, similar to what spring.data.rest.base-path does. – Buurman Oct 07 '19 at 12:23
  • Your answer is spot on @HaraldWendel :+1: You could improve upon it some more by expanding upon it a bit, like explaining what your code does exactly (as I've tried to do in my comment) and/or maybe link to some javadoc or documentation that describes this usage. – Buurman Oct 07 '19 at 12:31
  • This is the only solution that worked for me as I'm using controller interfaces – Anatoly Yakimchuk Apr 09 '20 at 15:11
  • This is the only right answer. It should be flagged as THE ANSWER. – Sixro Nov 17 '21 at 21:55
32

I couldn't believe how complicate the answer to this seemingly simple question is. Here are some references:

There are many differnt things to consider:

  1. By settingserver.context-path=/api in application.properties you can configure a prefix for everything.(Its server.context-path not server.contextPath !)
  2. Spring Data controllers annotated with @RepositoryRestController that expose a repository as rest endpoint will use the environment variable spring.data.rest.base-path in application.properties. But plain @RestController won't take this into account. According to the spring data rest documentation there is an annotation @BasePathAwareController that you can use for that. But I do have problems in connection with Spring-security when I try to secure such a controller. It is not found anymore.

Another workaround is a simple trick. You cannot prefix a static String in an annotation, but you can use expressions like this:

@RestController
public class PingController {

  /**
   * Simple is alive test
   * @return <pre>{"Hello":"World"}</pre>
   */
  @RequestMapping("${spring.data.rest.base-path}/_ping")
  public String isAlive() {
    return "{\"Hello\":\"World\"}";
  }
}
Community
  • 1
  • 1
Robert
  • 1,579
  • 1
  • 21
  • 36
28

Since this is the first google hit for the problem and I assume more people will search for this. There is a new option since Spring Boot '1.4.0'. It is now possible to define a custom RequestMappingHandlerMapping that allows to define a different path for classes annotated with @RestController

A different version with custom annotations that combines @RestController with @RequestMapping can be found at this blog post

@Configuration
public class WebConfig {

    @Bean
    public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerMapping() {
        return new WebMvcRegistrationsAdapter() {
            @Override
            public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
                return new RequestMappingHandlerMapping() {
                    private final static String API_BASE_PATH = "api";

                    @Override
                    protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
                        Class<?> beanType = method.getDeclaringClass();
                        if (AnnotationUtils.findAnnotation(beanType, RestController.class) != null) {
                            PatternsRequestCondition apiPattern = new PatternsRequestCondition(API_BASE_PATH)
                                    .combine(mapping.getPatternsCondition());

                            mapping = new RequestMappingInfo(mapping.getName(), apiPattern,
                                    mapping.getMethodsCondition(), mapping.getParamsCondition(),
                                    mapping.getHeadersCondition(), mapping.getConsumesCondition(),
                                    mapping.getProducesCondition(), mapping.getCustomCondition());
                        }

                        super.registerHandlerMethod(handler, method, mapping);
                    }
                };
            }
        };
    }
}
mh-dev
  • 5,264
  • 4
  • 25
  • 23
  • 2
    In Spring Boot 2.0.0+, work off the WebMvcRegistrations interface directly. WebMvcRegistrationsAdapter was removed in favor of adding default methods to the interface. – The Gilbert Arenas Dagger Aug 11 '18 at 19:35
23

You can create a custom annotation for your controllers:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
@RequestMapping("/test")
public @interface MyRestController {
}

Use it instead of the usual @RestController on your controller classes and annotate methods with @RequestMapping.

Just tested - works in Spring 4.2!

Ilya Novoseltsev
  • 1,803
  • 13
  • 19
  • Thank you. I've tried this. But now I have to annotate each method with @RequestMapping("/products"), @RequestMapping("/products/{id}"). Instead I need annotate Controller with @RequestMapping("/products") and methods with @RequestMapping, @RequestMapping("/:id"). And products controller should be accesable at api/products (e.g. set api prefix in a single place) – Teimuraz Oct 05 '15 at 10:31
  • 2
    In that case, no, there is no solution out of the box, AFAIK. You can try implementing your own `RequestMappingHandlerMapping`. Spring Data REST has a mapper similar to what you need - `BasePathAwareHandlerMapping`. – Ilya Novoseltsev Oct 05 '15 at 12:27
  • 1
    @moreo, did you find a proper solution? I'd be happy if you could post it as a response. i have the same requirement here. – fischermatte Dec 02 '15 at 11:02
  • @fischermatte, No, I didn't find exactly what I wanted, I place @RequestMapping("/api/products") or @RequestMapping("/api/users") before each controller class and then, before method just another @RequestMapping("/{id}"). But I don't think this is a big issue, if I want to change "api" to something, I will just change it in the beginning of each class. – Teimuraz Dec 02 '15 at 13:21
  • @IlyaNovoseltsev There is a solution, see my answer :-) – kravemir Jun 07 '16 at 14:11
  • tried a few configuration bean approaches and others but in the end this is the cleanest a most usable solution for me. thanks – phury Oct 06 '17 at 11:51
  • This is what i had been looking for, Thanks – Sasi Dunston Jan 18 '19 at 13:55
  • thats good. the other solutions were more complex and we dont need to – withoutOne Mar 22 '21 at 18:38
13

For Boot 2.0.0+ this works for me: server.servlet.context-path = /api

  • 4
    That put everything under /api it seems, not only @RestController mappers. But thanks anyway. Your information is still useful. – eigil Sep 05 '18 at 12:05
12

I did some research on the differences of spring properties mentioned in this thread. Here are my findings if anybody is wondering.

spring.data.rest.basePath Property

spring.data.rest.basePath=/api

This property is specifically for Spring Data Rest projects. It won't work in a usual Spring MVC projects.

To change the context path in MVC projects, you can use those two properties mentioned below. Let me mention the differences too.

server.servlet.context-path Property

server.servlet.context-path=/api

This one sets the context path on your web servelet. This property perfectly works fine in both spring mvc and spring data rest projects. But, the differnce is the request url will be filter out before reaching spring interceptors. So it will respond with HTML on bad request. Not Spring's or your own custom JSON response (in @ResponseBodyAdvice annotated class) defined. To overcome that, you should use this property below.

spring.mvc.servlet.path Property

spring.mvc.servlet.path=/api

This will filter the request URL in spring mvc interceptors and will respond default/your custom JSON response if you invoke a bad request.

Conclusion:

So as the OP's question, I would suggest that he should use spring.mvc.servlet.path to change the context path.

10

I found a clean solution, which affects only rest controllers.

@SpringBootApplication
public class WebApp extends SpringBootServletInitializer {

    @Autowired
    private ApplicationContext context;

    @Bean
    public ServletRegistrationBean restApi() {
        XmlWebApplicationContext applicationContext = new XmlWebApplicationContext();
        applicationContext.setParent(context);
        applicationContext.setConfigLocation("classpath:/META-INF/rest.xml");

        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setApplicationContext(applicationContext);

        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet, "/rest/*");
        servletRegistrationBean.setName("restApi");

        return servletRegistrationBean;
    }

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

Spring boot will register two dispatcher servlets - default dispatcherServlet for controllers, and restApi dispatcher for @RestControllers defined in rest.xml:

2016-06-07 09:06:16.205  INFO 17270 --- [           main] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'restApi' to [/rest/*]
2016-06-07 09:06:16.206  INFO 17270 --- [           main] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]

The example rest.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
  http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

    <context:component-scan base-package="org.example.web.rest"/>
    <mvc:annotation-driven/>

    <!-- Configure to plugin JSON as request and response in method handler -->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <ref bean="jsonMessageConverter"/>
            </list>
        </property>
    </bean>

    <!-- Configure bean to convert JSON to POJO and vice versa -->
    <bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
    </bean>
</beans>

But, you're not limited to:

  • use XmlWebApplicationContext, you may use any else context type available, ie. AnnotationConfigWebApplicationContext, GenericWebApplicationContext, GroovyWebApplicationContext, ...
  • define jsonMessageConverter, messageConverters beans in rest context, they may be defined in parent context
kravemir
  • 10,636
  • 17
  • 64
  • 111
  • Is it possible to do this programmatically without use of the xml? – Arian Nov 23 '17 at 19:13
  • 1
    @ArianHosseinzadeh Yes. It's possible to do it programmatically. There are many ways to setup spring context. In the example, I have shown how to create child context for REST API handling. Just learn how to setup context within Java, and then combine such knowledge with knowledge in this answer. That's called programming. – kravemir Nov 23 '17 at 22:04
7

I might be a bit late, BUT... I believe it is the best solution. Set it up in your application.yml (or similar config file):

spring:
    data:
        rest:
            basePath: /api

As I can remember that's it - all of your repositories will be exposed beneath this URI.

vault
  • 3,930
  • 1
  • 35
  • 46
thorinkor
  • 958
  • 1
  • 11
  • 20
  • Can you explain this a bit or point to a releavant documentation? – Dmitry Serdiuk Nov 25 '16 at 14:56
  • 1
    Relevant docs are at http://docs.spring.io/spring-data/rest/docs/current/reference/html/#_changing_the_base_uri. – bruce szalwinski Dec 14 '16 at 04:22
  • 14
    the environemnt variable `spring.data.rest.base-path` only affects spring-data-rest and spring-hateoas. Plain @RestController will still sit at the root! – Robert Jan 13 '17 at 14:08
  • @Robert of course, but I think currently in most cases You will use Spring Data REST Repositories instead of doing the controllers on your own. – thorinkor Jan 18 '17 at 15:03
  • 4
    @thorinkor based on what are you saying that in most cases people will build Spring Data REST repositories? And the OP is clearly saying he has rest controllers... – Jean-François Beauchef Jul 07 '17 at 19:46
  • @Jean-FrançoisBeauchef I think Spring Data REST gives a great oportunity to use it "out of the box" in many cases, instead of writing custom services, repositories and controllers. I've got a feeling this generally is a SpringBoot approach - first configure, than code. – thorinkor Jul 25 '17 at 10:02
  • 1
    I think it will only work if you are using SpringDataRest. – Jaumzera Dec 06 '17 at 18:46
  • Why colons, as opposed to periods? (The documentation uses periods.) – Woodchuck Apr 02 '19 at 20:20
5

You can create a base class with @RequestMapping("rest") annotations and extend all you other classes with this base class.

@RequestMapping("rest")
public abstract class BaseController {}

Now all classes that extend this base class will be accessible at rest/**.

Saket Mehta
  • 2,438
  • 2
  • 23
  • 28
  • 4
    This is not correct answer, the user is referring to Controller annotation. If you extend an abstract class with a RequestMapping annotation, and the new class has a RequestMapping too, this last will override the first one, it will not concatenate the two. – Massimo Dec 04 '17 at 15:19
  • Are you aware that annotations are not inherited in java unless it has inherited meta annotation? Check this: https://stackoverflow.com/a/21409651. And @RequestMapping does not seem to have that: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html – Mashrur Oct 10 '19 at 12:46
2

With spring-boot 2.x you can configure in application.properties:

spring.mvc.servlet.path=/api
bsk
  • 111
  • 4
2

For those who use YAML configuration(application.yaml).

Note: this works only for Spring Boot 2.x.x

server:
  servlet:
    contextPath: /api

If you are still using Spring Boot 1.x

server:
  contextPath: /api
Prasanth Rajendran
  • 4,570
  • 2
  • 42
  • 59
1

You can create a custom annotation for your controllers:

Use it instead of the usual @RestController on your controller classes and annotate methods with @RequestMapping.

Works fine in Spring 4.2!

1

server.servlet.context-path=/api would be the solution I guess. I had the same issue and this got me solved. I used server.context-path. However, that seemed to be deprecated and I found that server.servlet.context-path solves the issue now. Another workaround I found was adding a base tag to my front end (H5) pages. I hope this helps someone out there.

Cheers

Chameera Dulanga
  • 218
  • 5
  • 17
Rahul Talreja
  • 199
  • 2
  • 8
1

For Spring WebFlux the approach is similar to Harald's, but with the obvious WebFlux configuration set up:

@Configuration
public class WebFluxConfig implements WebFluxConfigurer  {

   @Override
   public void configurePathMatching(PathMatchConfigurer configurer) {
       configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
   }
}

And for Kotlin it's:

@Configuration
class WebFluxConfig : WebFluxConfigurer {
    override fun configurePathMatching(configurer: PathMatchConfigurer) {
       configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController::class.java))
}
Gregra
  • 773
  • 8
  • 22
0

This solution applies if:

  1. You want to prefix RestController but not Controller.
  2. You are not using Spring Data Rest.

    @Configuration
    public class WebConfig extends WebMvcConfigurationSupport {
    
    @Override
    protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
        return new ApiAwareRequestMappingHandlerMapping();
    }
    
    private static class ApiAwareRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
    
        private static final String API_PATH_PREFIX = "api";
    
        @Override
        protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
            Class<?> beanType = method.getDeclaringClass();
            if (AnnotationUtils.findAnnotation(beanType, RestController.class) != null) {
                PatternsRequestCondition apiPattern = new PatternsRequestCondition(API_PATH_PREFIX)
                        .combine(mapping.getPatternsCondition());
    
                mapping = new RequestMappingInfo(mapping.getName(), apiPattern, mapping.getMethodsCondition(),
                        mapping.getParamsCondition(), mapping.getHeadersCondition(), mapping.getConsumesCondition(),
                        mapping.getProducesCondition(), mapping.getCustomCondition());
            }
            super.registerHandlerMethod(handler, method, mapping);
        }
    }
    

    }

This is similar to the solution posted by mh-dev, but I think this is a little cleaner and this should be supported on any version of Spring Boot 1.4.0+, including 2.0.0+.

The Gilbert Arenas Dagger
  • 12,071
  • 13
  • 66
  • 80
  • If i'm using Pageable in my RestControler, api/something gives me No primary or default constructor found for interface org.springframework.data.domain.Pageable – K. Ayoub Aug 12 '18 at 13:35
0

Per Spring Data REST docs, if using application.properties, use this property to set your base path:

spring.data.rest.basePath=/api

But note that Spring uses relaxed binding, so this variation can be used:

spring.data.rest.base-path=/api

... or this one if you prefer:

spring.data.rest.base_path=/api

If using application.yml, you would use colons for key separators:

spring:
  data:
    rest:
      basePath: /api

(For reference, a related ticket was created in March 2018 to clarify the docs.)

Woodchuck
  • 3,869
  • 2
  • 39
  • 70
-2

worked server.contextPath=/path

Pravin Bansal
  • 4,315
  • 1
  • 28
  • 19