Spring Boot - Swagger Documentation doesn't work
Asked Answered
C

3

5

I have an REST API project build with Spring Boot and i want to document all my Endpoints. I have implemented swagger to do it and was successfully, but recently my project doesn't run anymore, the configuration is the same that when create the project and Swagger was works.

I'm getting this error when try to run the project:

WARN 17868 --- [  restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
....
ERROR 17868 --- [  restartedMain] o.s.boot.SpringApplication               : Application run failed

org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException

What am i doing wrong?

This is my project configuration:

Main

package com.red.api;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
@PropertySource(value = "classpath:application.properties")
@PropertySource(value = "classpath:propiedades.properties")
public class BackRedApplication {

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

    @Bean
    public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
       return new PropertySourcesPlaceholderConfigurer();
    }
}

Pom.xml

...
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.0-SNAPSHOT</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.red.api</groupId>
<artifactId>RED</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>RED</name>
<description>RED</description>
<properties>
    <java.version>11</java.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-ldap</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jersey</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-boot-starter</artifactId>
        <version>3.0.0</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>3.0.0</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.json</groupId>
        <artifactId>json</artifactId>
        <version>20200518</version>
    </dependency>
    <dependency>
        <groupId>net.sf.jasperreports</groupId>
        <artifactId>jasperreports</artifactId>
        <version>6.8.0</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>io</artifactId>
        <version>7.1.9</version>
    </dependency>
    
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>kernel</artifactId>
        <version>7.1.9</version>
    </dependency>
    
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>layout</artifactId>
        <version>7.1.9</version>
    </dependency>
</dependencies>
...

Configuration Class

package com.red.api.configuracion;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.databind.ObjectMapper;

import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;


@Configuration
@EnableSwagger2
public class Configuracion {

    @Bean
    public RestTemplate rest() {
        return new RestTemplate();
    }

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }

    @Bean
    public Docket redApi() {
        return new Docket(DocumentationType.SWAGGER_2).select()
                .apis(RequestHandlerSelectors.basePackage("com.co.dejsoftware.red.ws"))
                .paths(PathSelectors.any()).build();
    }

}

Controller

package com.red.api.ws;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.red.api.interfaces.InterfaceSeguridad;

@RestController
@CrossOrigin
@RequestMapping("/seguridad")
public class WsSeguridad {

    private Logger logger = LogManager.getLogger(WsSeguridad.class);

    @Autowired
    private InterfaceSeguridad servicioSeguridad;

    @PostMapping(path="/getToken", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Object> getToken(@RequestHeader(name = "user") String usuario, 
            @RequestHeader(name = "pwd") String contrasena) {
    
        try {
        
            return ResponseEntity.ok(servicioSeguridad.getToken(usuario, contrasena));
        
        } catch (Exception e) {
            logger.error(e);
        
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
}

Interface

package com.red.api.interfaces;

import org.springframework.http.ResponseEntity;

public interface InterfaceSeguridad {

    public ResponseEntity<Object> getToken(String usuario, String contrasena);
}

Implementation

package com.red.api.implementacion;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import com.red.api.interfaces.InterfaceSeguridad;

@Service
public class ServicioSeguridad implements InterfaceSeguridad {

    private Logger logger = LogManager.getLogger(ServicioSeguridad.class);

    public ResponseEntity<Object> getToken(String usuario, String contrasena) {
        try {
            System.out.println("usuario... " + usuario);
            System.out.println("contrasena... " + contrasena);
        
            JSONObject item = new JSONObject();
            item.put("description", "Success...");
            item.put("usuario", usuario);
            item.put("contrasena", contrasena);
            String jsonResponse = new JSONObject().put("exito", item).toString();
        
            return ResponseEntity.ok(jsonResponse);
        }catch (Exception e) {
            logger.error(e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
}
Cabral answered 8/9, 2021 at 18:49 Comment(3)
Have you changed springfox version?Blacklist
According to docs you should remove @EnableSwagger2 annotation when using springfox 3.Terrify
the project settings have been the same since the project was created I removed the @ EnableSwagger2 annotation but still the same errorCabral
G
8

I know this does not solve your problem directly, but consider moving to springdoc. Springfox is so buggy at this point that is a pain to use. I've moved to springdoc 2 years ago because of its Spring WebFlux support and I am very happy about it. Additionally, it also supports Kotlin Coroutines, which I am not sure Springfox does.

If you decide to migrate, springdoc even has a migration guide.

Griceldagrid answered 8/9, 2021 at 19:44 Comment(8)
If i decide to use springdoc could i do the same as Swagger?Cabral
Documenting your API following OpenAPI Specification? Sure, that is what it does. Unless you have some very weird use case with Springfox that springdoc does not support (which I doubt) I would say you are fine moving. But give it a try and check it.Blacklist
One additional thing that I forgot to mention is that the response time of a created issue in springdoc github page is usually low and valid issues are tackled in a very fast manner.Blacklist
thanks again, I will do the tests with springdoc, it is strange that the project with springfox does not work if it already worked previouslyCabral
I have made the migration to springdoc and it is more easier to configure, for now I will continue using it. Once again thanks. @João DiasCabral
I also agree on it being easier to configure, which is highly appreciated. You are welcome! If it helped, you can upvote the answer and even accept it as the correct one ;)Blacklist
@JoãoDias thanks for this plugin, I was struggling with springfox with my project for 2 weeks now, when I switched to springdoc , it took me less than 5 min to configure and get swagger up and running, thank you once againHonaker
@elpidaguy, great! springdoc is way easier to configure and use. The best thing is that it actually just works, which is something we can't really say about springfox.Blacklist
P
4

The problem seems to be that Spring Boot switched from Ant Path Matching to using a PathPatternParser, see

The Springfox plugin has not been actively maintained for a while and is thus unable to deal with request mappings with the PathPatternParser. At least in version 3.0.0.

If one insists on continuing to use Springfox with Spring Boot >= 2.6, one can try to force use of Ant Path Matching by setting

spring.mvc.pathmatch.matching-strategy=ant_path_matcher

Forcing Ant Path Matching on the actuators is a separate problem. It works by injecting the WebMvcEndpointHandlerMapping that was auto-configured before the change by WebMvcEndpointManagementContextConfiguration:

@Bean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(
    WebEndpointsSupplier webEndpointsSupplier,
    ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier,
    EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
    WebEndpointProperties webEndpointProperties, Environment environment) {
  List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
  Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
  allEndpoints.addAll(webEndpoints);
  allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
  allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
  String basePath = webEndpointProperties.getBasePath();
  EndpointMapping endpointMapping = new EndpointMapping(basePath);
  boolean shouldRegisterLinksMapping = shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
  return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes,
      corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath),
      shouldRegisterLinksMapping);
}

private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment,
    String basePath) {
  return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath)
      || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
}

There may be a cleverer way by excluding the actuators from being analyzed by Springfox in the first place.

You're mileage may vary. Switching to springdoc is probably the more worthwhile approach.

Pratt answered 2/11, 2021 at 17:59 Comment(0)
B
0

I had a similar problem, I was trying to upgrade to Spring 2.6.6.

I suggest using openapi generator >= 5.4.0 as it removes springfox as default documentation provider

https://github.com/OpenAPITools/openapi-generator/pull/11181 fixes https://github.com/OpenAPITools/openapi-generator/issues/10409

springdoc is the default

more info on spring boot generator options, see https://openapi-generator.tech/docs/generators/spring/ and search for documentationProvider

Burrill answered 5/4, 2022 at 11:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.