Using Swagger 2 with SpringFox and JAX-RS in a SpringBoot app

Swagger 2 is the defacto API documentation tool that is used in many Spring boot applications. Most of the examples for using swagger with spring boot are with springMVC. There are certain use cases where you would be using spring boot with JAX-RS. Is it possible to use Swagger 2 with SpringBoot and JAX-RS applications?

While Swagger 2 and SpringMVC integration is straight forward with spring annotations, integration with JAX-RS annotations is a bit involved. We are going to look at how to do this.

This starts with a simple spring boot project.

swagger1

Download the project and setup your workspace. This sample is done using IntelliJ IDEA. Now lets add the following  dependencies. Notice that I have switched to spring boot 1.2.2 for this example.

 

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-jersey</artifactId>
   <version>1.2.2.RELEASE</version>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <version>1.2.2.RELEASE</version>
</dependency>

<!-- swagger and spring fox dependencies -->
<dependency>
   <groupId>io.swagger</groupId>
   <artifactId>swagger-jersey2-jaxrs</artifactId>
   <version>1.5.13</version>
</dependency>
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger2</artifactId>
   <version>2.7.0</version>
</dependency>
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger-ui</artifactId>
   <version>2.7.0</version>
</dependency>

Create classes and packages as shown in the image below.

swagger2

Lets look at the classes in detail.

DocApplication is the main class that loads the application, defines the SpringFox Docket and ApiInfo objects. The @EnableSwagger2 annotation is required to use Swagger 2. The api() would be called internally when spring boot app is loaded and this in turn calls the apiInfo() along with setting the path and resources to be scanned for Swagger annotations.

package com.atech.spring.swagger.doc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.StringVendorExtension;
import springfox.documentation.service.VendorExtension;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.Collection;

/**
 * Created by atech on 1/21/18.
 */
@SpringBootApplication
@EnableSwagger2
public class DocApplication extends SpringBootServletInitializer {

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

   public Docket api() {
      return new Docket(DocumentationType.SWAGGER_2)
            .select()            .apis(RequestHandlerSelectors.basePackage("com.atech.spring.swagger.doc.controller"))
            .paths(PathSelectors.any())
            .build()
            .enable(true)
            .apiInfo(apiInfo());
   }

   public ApiInfo apiInfo() {
     StringVendorExtension vendorExtension = new StringVendorExtension("", "");
     Collection<VendorExtension> vendorExtensions = new ArrayList<>();
     vendorExtensions.add(vendorExtension);

     Contact contactInfo = new Contact("AtechRef", "www.atechref.com",
            "atechsoft@gmail.com");

     return new ApiInfo(
         "SpringBoot-Swagger2-JaxRS",
         "Example project showing how to integrate spring boot " +
               "web app using jaxrs instead of springmvc with swagger and springfox.",
         "1.0",
         "For Demo only",
         contactInfo,
         "Apache 2.0",
         "www.apache.org",
         vendorExtensions);
   }

}

The second class ties in jersey with swagger as shown below.

package com.atech.spring.swagger.doc;

import io.swagger.jaxrs.config.BeanConfig;
import io.swagger.jaxrs.config.SwaggerConfigLocator;
import io.swagger.jaxrs.config.SwaggerContextService;
import io.swagger.jaxrs.listing.ApiListingResource;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

import javax.ws.rs.ApplicationPath;

/**
 * Created by atech on 1/21/18.
 */
@Component
@ApplicationPath("/api")
public class JerseyConfig extends ResourceConfig {

 public JerseyConfig() {
    BeanConfig swaggerConfig = new BeanConfig();
    swaggerConfig.setBasePath("/api");
              SwaggerConfigLocator.getInstance().putConfig(SwaggerContextService.CONFIG_ID_DEFAULT, swaggerConfig);

     packages(getClass().getPackage().getName(),
                  ApiListingResource.class.getPackage().getName());
  }
}

The final class that is required to be configured is the CombinedResourceProvider.java that defines springfox binding

package com.atech.spring.swagger.doc;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.InMemorySwaggerResourcesProvider;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Created by atech on 1/21/18.
 */
@Component
@Primary
public class CombinedResourceProvider implements SwaggerResourcesProvider {

    @Resource
    private InMemorySwaggerResourcesProvider inMemorySwaggerResourcesProvider;

    public List<SwaggerResource> get() {

        SwaggerResource jerseySwaggerResource = new SwaggerResource();
        jerseySwaggerResource.setLocation("/api/swagger.json");
        jerseySwaggerResource.setSwaggerVersion("2.0");
        jerseySwaggerResource.setName("Jersey");

        return Stream.concat(Stream.of(jerseySwaggerResource),
                inMemorySwaggerResourcesProvider.get().stream()).collect(Collectors.toList());
    }

}

The controller is below and note the annotations in bold. Using the @Api, @ApiOperation and @ApiResponse annotations allows us to document the API in the code like java docs.

package com.atech.spring.swagger.doc.controller;

import com.atech.spring.swagger.doc.model.Hello;
import io.swagger.annotations.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

/**
 * Created by atech on 1/21/18.
 */
@Component
@Path("/v1/hello")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Api(value = "Hello API - say hello to the world", produces = "application/json")
public class HelloWorld {

        private static final Logger LOGGER = LoggerFactory.getLogger(HelloWorld.class);

        @GET                //JAX-RS Annotation
        @Path("/{name}")   //JAX-RS Annotation
        @ApiOperation(            //Swagger Annotation
                value = "Say hello by entering your name",
                response = Hello.class)
        @ApiResponses(value = {       //Swagger Annotation
                @ApiResponse(code = 200, message = "Resource found"),
                @ApiResponse(code = 404, message = "Resource not found")
        })
        public Response sayHello(@ApiParam @PathParam("name") String name) {
            LOGGER.info("v1/hello/{} - {}", name, MediaType.APPLICATION_JSON);
            return this.buildResponse(name, MediaType.APPLICATION_JSON);
        }

        private Response buildResponse(String name, String via) {
            //handle 404
            if ("404".equals(name)) {
                return Response.status(Response.Status.NOT_FOUND).build();
            }

            Hello result = new Hello();
            result.setMsg(String.format("Hello %s - %s", name, via));
            return Response.status(Response.Status.OK).entity(result).build();
        }
    }

And the model class is below

package com.atech.spring.swagger.doc.model;

/**
 * Created by atech on 1/21/18.
 */
public class Hello {

    private String msg;

    public String getMsg() {
        return this.msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

}

Now when this app is built and run we can access swagger UI using http://localhost:8080/swagger-ui.html. It should look like below.

swagger3

Expand the API and review the documentation that was entered in the controller code in the java file render in a nice UI . You can even try out the API by entering the required information and submitting a request that is processed in real time by the spring boot app.

swagger5 swagger6

How cool is that? We have a fully functional Swagger 2 UI that works with only annotations for SpringBoot and JAX-RS RESTful service.

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *