davids182009
davids182009

Reputation: 451

Spring Boot - Swagger Documentation doesn't work

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();
        }
    }
}

Upvotes: 5

Views: 12249

Answers (3)

Tony Murphy
Tony Murphy

Reputation: 751

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

Upvotes: 0

Henning
Henning

Reputation: 3899

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.

Upvotes: 4

Jo&#227;o Dias
Jo&#227;o Dias

Reputation: 17510

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.

Upvotes: 8

Related Questions