Atlassian Swagger Request Validator

Spring Boot Validate API Requests and Responses with Atlassian Swagger Request Validator

Learn how to use Atlassian Swagger Request Validator to validate API requests and responses in case of Spring Boot applications.

1. Introduction

If you’ve ever worked with APIs, you know how crucial it is that they behave exactly as expected. When an API doesn’t follow its contract, it can lead to frustrating debugging sessions, broken client applications, and unhappy users. This is where API validation comes in – ensuring that the requests and responses conform to the API specification you’ve defined.

In this blog post, we’ll explore Atlassian’s swagger-request-validator library, a powerful library that helps you validate your API interactions against OpenAPI specifications. Whether you’re new to API development or looking to improve your validation strategy, this guide will help you understand how to implement effective API validation.

2. Why API Request and Response Validation Is Important?

Before diving into the technical details, let’s understand why validation matters:

  • Prevents Unexpected Behavior: Without validation, APIs might accept invalid inputs or return malformed responses, leading to unexpected behavior in client applications.
  • Improves Developer Experience: Clear validation errors help developers quickly identify and fix issues, making your API more developer-friendly.
  • Ensures Backward Compatibility: Validation helps maintain backward compatibility by ensuring API changes don’t break existing clients.
  • Enhances Security: Proper validation can prevent certain types of security vulnerabilities, like injection attacks or data exposure.
  • Maintains Data Quality: Validating data formats and structures ensures that your application receives and provides quality data.


3. What Is Atlassian Swagger Request Validator?

Atlassian swagger request validator is a Java library designed to validate HTTP requests and responses against OpenAPI specifications (formerly known as Swagger). It allows you to:

  • Confirm that API calls conform to your specification
  • Validate both requests and responses
  • Create custom validation rules for complex scenarios
  • Integrate validation into your testing pipeline

This library is particularly valuable when:

  • Building new APIs and wanting to ensure they adhere to their specifications
  • Testing existing APIs to verify compliance
  • Setting up automated testing for continuous integration

4. What Can It Validate?

The library supports a range of validations out-of-the-box. Here are some key ones:

  • Schema Validation:
    • Data Types: Ensure that fields have the correct data types (e.g., strings, integers, booleans).
    • Formats: Validate specific formats like email addresses, dates, and UUIDs.
    • Patterns: Check that strings match regular expressions (e.g., zip codes).
    • Minimum/Maximum Values: For numeric data, ensure values fall within the allowed range.
    • Array and Object Structures: Validate nested objects and arrays based on the defined schema.
  • Required Fields:
    • Ensures that all mandatory fields are provided in the request or response.
  • Enum Validation:
    • Checks that values are one of the pre-defined options.
  • Content Type Validation:
    • Verifies that the request or response has the expected content type (e.g., application/json).
  • Path and Query Parameter Validation:
    • Ensures that path variables and query parameters conform to the specification.

For a complete list of supported features refer:



5. Integration with Spring Boot

With its Spring integration, you can easily plug the validation mechanism into your Spring Boot application.

Example:

We’re building a Spring Boot API for managing products in an e-commerce system, integrated with Atlassian Swagger Request Validator. We validate both incoming requests and outgoing responses against our OpenAPI specification to ensure strict adherence to our API contract, and we handle validation errors gracefully using a global error handler.

Step 1: Add Swagger Request Validator dependency to your Spring Boot maven project

Add the following dependencies to your Maven project’s pom.xml. This dependency provides the Spring Web MVC integration needed to automatically validate your requests and responses.

<dependency>
  <groupId>com.atlassian.oai</groupId>
  <artifactId>swagger-request-validator-spring-webmvc</artifactId>
  <version>2.44.1</version>
</dependency>

<!-- Provides Jakarta Bean Validation implementation (Hibernate Validator) - Optional -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
pom.xml


At the time of writing, the latest version is 2.44.1. To check for the most recent version, click here.

Note that the spring-boot-starter-validation dependency has been added to avoid the NoProviderFoundException error message during the application startup. This is optional.


Step 2: Create a sample OpenAPI spec for e-commerce product API

Create an OpenAPI specification file (e.g., ecommerce-product-api.yaml) in src/main/resources with your API details. For example:

openapi: 3.0.3
info:
  title: E-commerce API
  version: "1.0.0"
  description: API for managing products in an e-commerce system.
paths:
  /products:
    post:
      summary: Create a New Product
      operationId: createProduct
      requestBody:
        required: true
        description: Product information needed to create a new product.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductRequest'
      responses:
        '201':
          description: Product created successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductResponse'
        '400':
          description: Invalid product data provided.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /products/{productId}:
    get:
      summary: Get Product by ID
      operationId: getProduct
      parameters:
        - name: productId
          in: path
          required: true
          description: Unique identifier of the product.
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Product details retrieved successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductResponse'
        '404':
          description: Product not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    ProductRequest:
      type: object
      required:
        - name
        - price
        - category
      properties:
        name:
          type: string
          description: Name of the product.
          minLength: 3
          maxLength: 50
        price:
          type: number
          format: float
          description: Price of the product. Must be greater than 0.
          minimum: 0.01
        description:
          type: string
          description: Detailed description of the product.
          maxLength: 500
        category:
          $ref: '#/components/schemas/Category'
        tags:
          type: array
          description: List of tags for the product.
          items:
            type: string
        attributes:
          type: object
          description: A map of additional attribute names and values.
          additionalProperties:
            type: string
    ProductResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier of the product.
        name:
          type: string
          description: Name of the product.
        price:
          type: number
          description: Price of the product.
        description:
          type: string
          description: Detailed description of the product.
        category:
          $ref: '#/components/schemas/Category'
        tags:
          type: array
          description: List of tags associated with the product.
          items:
            type: string
        attributes:
          type: object
          description: Additional attributes provided for the product.
          additionalProperties:
            type: string
        createdAt:
          type: string
          description: Timestamp when the product was created.
    Category:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
          description: Unique identifier of the category.
        name:
          type: string
          description: Name of the category.
        parentCategory:
          $ref: '#/components/schemas/CategoryReference'
          description: Reference to the parent category, if any.
        subCategories:
          type: array
          description: List of sub-categories.
          items:
            $ref: '#/components/schemas/CategoryReference'
    CategoryReference:
      type: object
      properties:
        id:
          type: integer
          description: Unique identifier of the category.
        name:
          type: string
          description: Name of the category.
    ErrorResponse:
      type: object
      properties:
        code:
          type: string
          description: Error code.
        details:
          type: array
          description: Additional error details.
          items:
            type: string
ecommerce-product-api.yaml

Step 3: Configure Filter & Interceptor

To integrate the validator into your Spring Boot application, you need to add both a filter and an interceptor. This configuration ensures that validation occurs for every incoming request and response.

This configuration class automatically checks every API request and response against our defined API contract. The API contract is registered from the ecommerce-product-api.yaml file located in the resources folder, ensuring that our application strictly adheres to the expected data formats and rules.

import com.atlassian.oai.validator.springmvc.OpenApiValidationFilter;
import com.atlassian.oai.validator.springmvc.OpenApiValidationInterceptor;
import jakarta.servlet.Filter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Configuration
public class OpenApiValidationConfig {

    // Register a filter that validates both requests and responses.
    @Bean
    public Filter validationFilter() {
        return new OpenApiValidationFilter(
                true, // enable request validation
                true  // enable response validation
        );
    }

    // Register an interceptor using the OpenAPI specification file.
    @Bean
    public WebMvcConfigurer addOpenApiValidationInterceptor(
            @Value("classpath:ecommerce-product-api.yaml") final Resource apiSpecification) throws IOException {
        final EncodedResource encodedResource = new EncodedResource(apiSpecification, StandardCharsets.UTF_8);
        final OpenApiValidationInterceptor openApiValidationInterceptor = new OpenApiValidationInterceptor(encodedResource);

        return new WebMvcConfigurer() {
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(openApiValidationInterceptor);
            }
        };
    }
}
OpenApiValidationConfig.java


Step 4: Create a controller class defining our APIs

This controller provides two basic endpoints under /products—one for creating a product and one for retrieving a product by ID. It simply returns simulated product data to demonstrate functionality, while our validation filter and interceptor (configured earlier) automatically check that the request and response match our API contract from the YAML file.

import com.bootcamptoprod.spring_boot_swagger_request_response_validator_demo.model.ProductRequest;
import com.bootcamptoprod.spring_boot_swagger_request_response_validator_demo.model.ProductResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.UUID;

@RestController
@RequestMapping("/products")
public class ProductController {

    // Create a new product
    @PostMapping
    public ResponseEntity<ProductResponse> createProduct(@RequestBody ProductRequest productRequest) {
        // For demo purposes, simulate product creation.
        ProductResponse response = new ProductResponse();
        response.setId(UUID.randomUUID().toString());
        response.setName(productRequest.getName());
        response.setPrice(productRequest.getPrice());
        response.setDescription(productRequest.getDescription());
        response.setCategory(productRequest.getCategory());
        response.setTags(productRequest.getTags());
        response.setAttributes(productRequest.getAttributes());
        response.setCreatedAt(LocalDateTime.now());

        return new ResponseEntity<>(response, HttpStatus.CREATED);
    }

    // Retrieve a product by ID
    @GetMapping("/{productId}")
    public ResponseEntity<ProductResponse> getProduct(@PathVariable String productId) {

        // Simulate an error scenario wherein many fields are missing in api response
        ProductResponse response = new ProductResponse();
        response.setId(productId);
        response.setName("Demo Product");
        response.setDescription("This is a demo product.");
        response.setCreatedAt(LocalDateTime.now());

        return new ResponseEntity<>(response, HttpStatus.OK);
    }
}
ProductController.java

Step 5: Create required POJO classes

Below POJOs define the data structure for our API. The ProductRequest and ProductResponse classes represent the input and output for product operations. These classes ensure that our API data matches the expected schema defined in our API contract.

public class ProductRequest {

    private String name;
    private BigDecimal price;
    private String description;
    private Category category;
    private List<String> tags;
    private Map<String, String> attributes;
    
    // Getters, setters, constructors

}

public class Category {

    private Integer id;
    private String name;
    private CategoryReference parentCategory;
    private List<CategoryReference> subCategories;
    
    // Getters, setters, constructors
}

public class CategoryReference {

    private Integer id;
    private String name;
    
    // Getters, setters, constructors
}

public class ProductResponse {

    private String id;
    private String name;
    private BigDecimal price;
    private String description;
    private Category category;
    private List<String> tags;
    private Map<String, String> attributes;
    private LocalDateTime createdAt;
    
    // Getters, setters, constructors
}

public class ErrorResponse {

    private String code;
    private Object details;
    
     // Getters, setters, constructors
}
POJOs

Step 6: Global Exception Handler

The global error controller catches validation exceptions—both for invalid requests and responses—that are thrown when the API data doesn’t meet the OpenAPI contract. The validator generates a validation report containing detailed error messages that explain which parts of the request or response failed. Our error handler extracts these messages, logs them, and returns a simplified error response with a 400 status for invalid requests and a 500 status for invalid responses.

import com.atlassian.oai.validator.report.ValidationReport;
import com.atlassian.oai.validator.springmvc.InvalidRequestException;
import com.atlassian.oai.validator.springmvc.InvalidResponseException;
import com.bootcamptoprod.spring_boot_swagger_request_response_validator_demo.model.ErrorResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.util.List;

@ControllerAdvice
public class GlobalErrorController extends ResponseEntityExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalErrorController.class);

    @ExceptionHandler(InvalidRequestException.class)
    public ResponseEntity<ErrorResponse> handleInvalidRequest(InvalidRequestException ex) {
        // Extract only the message field from each validation error
        List<String> errorMessages = ex.getValidationReport().getMessages()
                .stream()
                .map(ValidationReport.Message::getMessage).toList();
        log.error("Request validation error: {}", errorMessages);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(new ErrorResponse("INVALID_REQUEST", errorMessages));
    }

    @ExceptionHandler(InvalidResponseException.class)
    public ResponseEntity<ErrorResponse> handleInvalidResponse(InvalidResponseException ex) {
        // Extract only the message field from each validation error
        List<String> errorMessages = ex.getValidationReport().getMessages()
                .stream()
                .map(ValidationReport.Message::getMessage).toList();
        log.error("Response validation error: {}", errorMessages);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(new ErrorResponse("INVALID_RESPONSE", errorMessages));
    }
}
GlobalErrorController.java


Step 7: Verify the Output

For products API, we will test three scenarios:

a. Valid Request / Valid Response

In case of create products API, when all input data is correct, the request is successfully validated and processed.

curl --location 'http://localhost:8080/products' \
--header 'Content-Type: application/json' \
--data '{
    "name": "Sample Product",
    "price": 19.99,
    "description": "This is a sample product with all required details.",
    "category": {
        "id": 1,
        "name": "Electronics",
        "parentCategory": {
            "id": 0,
            "name": "Root"
        },
        "subCategories": [
            {
                "id": 2,
                "name": "Mobile Phones"
            }
        ]
    },
    "tags": [
        "new",
        "featured"
    ],
    "attributes": {
        "color": "blue",
        "warranty": "2 years"
    }
}'
Request cURL

Output:


b. Invalid Request

In case of create products API, when the incoming request fails to meet the API schema requirements, the validator returns a 400 Bad Request error along with details about the validation issues.

curl --location 'http://localhost:8080/products' \
--header 'Content-Type: application/json' \
--data '{
  "name": "Pr", 
  "price": 0,
  "description": "This product has multiple validation issues.",
  "category": {
    "id": "one",
    "name": ""
  },
  "tags": [123, true],
  "attributes": {
    "color": 1234
  }
}
'
Request cURL

Output:


c. Invalid Response

Sometimes the API may produce a response that does not conform to the OpenAPI specification. In such cases, the response validation fails, and the validator will throw an InvalidResponseException. The global error handler intercepts this and returns a 500 Internal Server Error with details of the validation issues.

Simulated Scenario: Imagine your controller accidentally returns a response in which required fields are missing.

curl --location 'http://localhost:8080/products/a40f24ff-a102-482d-932d-66c9525cb343' \
--header 'Content-Type: application/json'
Request Body

Output:



6. Sending Detailed Error Responses

In our earlier example, we simplified the error response and sent only error messages in API response but If you want to return the complete error details (i.e. the full objects returned by ex.getValidationReport().getMessages()), you can simply do the following in your exception handler:

return ResponseEntity.status(HttpStatus.BAD_REQUEST)
        .body(new ErrorResponse("INVALID_REQUEST", ex.getValidationReport().getMessages()));
Java

For example, a full detail response might look like this:

{
    "code": "INVALID_RESPONSE",
    "details": [
        {
            "key": "validation.response.body.schema.type",
            "level": "ERROR",
            "message": "[Path '/attributes'] Instance type (null) does not match any allowed primitive type (allowed: [\"object\"])",
            "additionalInfo": [],
            "nestedMessages": [],
            "context": {
                "requestPath": "/products/a40f24ff-a102-482d-932d-66c9525cb343",
                "parameter": null,
                "apiRequestContentType": null,
                "responseStatus": 200,
                "location": "RESPONSE",
                "pointers": {
                    "instance": "/attributes",
                    "schema": "/properties/attributes"
                },
                "requestMethod": "GET",
                "appliedWhitelistRule": null
            }
        }
    ]
}
Error Response

7. Configuring the Default Error Handler to Include Details

If you don’t implement a custom global exception handler and rely on Spring Boot’s default error handling, your API responses may not include validation error details. To ensure that error messages (such as those from the validation report) are returned to the API consumer, add the following property to your application.properties file:

server.error.include-message=always
application.properties

This setting forces the default error handler to include detailed error messages in the API response, making it easier for clients to understand why their request failed. Without this configuration, the default response might omit important validation details.

Output:

When the property value is set to always

{
    "timestamp": "2025-03-15T17:58:59.774+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "message": "{\n  \"messages\" : [ {\n    \"key\" : \"validation.response.body.schema.type\",\n    \"level\" : \"ERROR\",\n    \"message\" : \"[Path '/attributes'] Instance type (null) does not match any allowed primitive type (allowed: [\\\"object\\\"])\",\n    \"context\" : {\n      \"requestPath\" : \"/products/a40f24ff-a102-482d-932d-66c9525cb343\",\n      \"responseStatus\" : 200,\n      \"location\" : \"RESPONSE\",\n      \"pointers\" : {\n        \"instance\" : \"/attributes\",\n        \"schema\" : \"/properties/attributes\"\n      },\n      \"requestMethod\" : \"GET\"\n    }\n  }, {\n    \"key\" : \"validation.response.body.schema.type\",\n    \"level\" : \"ERROR\",\n    \"message\" : \"[Path '/category'] Instance type (null) does not match any allowed primitive type (allowed: [\\\"object\\\"])\",\n    \"context\" : {\n      \"requestPath\" : \"/products/a40f24ff-a102-482d-932d-66c9525cb343\",\n      \"responseStatus\" : 200,\n      \"location\" : \"RESPONSE\",\n      \"pointers\" : {\n        \"instance\" : \"/category\",\n        \"schema\" : \"/properties/category\"\n      },\n      \"requestMethod\" : \"GET\"\n    }\n  }, {\n    \"key\" : \"validation.response.body.schema.type\",\n    \"level\" : \"ERROR\",\n    \"message\" : \"[Path '/price'] Instance type (null) does not match any allowed primitive type (allowed: [\\\"integer\\\",\\\"number\\\"])\",\n    \"context\" : {\n      \"requestPath\" : \"/products/a40f24ff-a102-482d-932d-66c9525cb343\",\n      \"responseStatus\" : 200,\n      \"location\" : \"RESPONSE\",\n      \"pointers\" : {\n        \"instance\" : \"/price\",\n        \"schema\" : \"/properties/price\"\n      },\n      \"requestMethod\" : \"GET\"\n    }\n  }, {\n    \"key\" : \"validation.response.body.schema.type\",\n    \"level\" : \"ERROR\",\n    \"message\" : \"[Path '/tags'] Instance type (null) does not match any allowed primitive type (allowed: [\\\"array\\\"])\",\n    \"context\" : {\n      \"requestPath\" : \"/products/a40f24ff-a102-482d-932d-66c9525cb343\",\n      \"responseStatus\" : 200,\n      \"location\" : \"RESPONSE\",\n      \"pointers\" : {\n        \"instance\" : \"/tags\",\n        \"schema\" : \"/properties/tags\"\n      },\n      \"requestMethod\" : \"GET\"\n    }\n  } ]\n}",
    "path": "/products/a40f24ff-a102-482d-932d-66c9525cb343"
}
Error Response

When the property is not set

{
    "timestamp": "2025-03-15T17:59:57.623+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/products/a40f24ff-a102-482d-932d-66c9525cb343"
}
Error Response


8. Enabling Debug Logging for the Validator

You can enable detailed logging for the Swagger Request Validator by adding the following line to your application.properties file:

logging.level.com.atlassian.oai.validator.springmvc=DEBUG
application.properties

This allows you to see internal validation steps and messages in the logs, which can be very helpful for troubleshooting and understanding how the validator is processing your requests and responses.

9. Source Code

The complete source code of the above example can be found here.



10. Things to Consider

  • Keep Your API Spec Updated: Regularly update your OpenAPI specification to ensure it accurately reflects the current API design and validation rules.
  • Use a Global Error Handler: Implement a global exception handler to catch validation errors and return clear, consistent error messages to API consumers.
  • Include Detailed Error Messages Cautiously: Detailed error messages can help with debugging, but they might expose sensitive information in production environments, so use them with care.
  • Enable Debug Logging: Use logging.level.com.atlassian.oai.validator.springmvc=DEBUG to log details of the validation process, aiding in troubleshooting.
  • Validate Both Requests and Responses: Ensure that both incoming requests and outgoing responses adhere to the defined API contract for complete data integrity.
  • Handle Nested Structures: Pay special attention to validating nested objects and arrays, as these often have more complex validation rules.
  • Performance Considerations: Understand that validation can add overhead, so monitor performance and optimize your validation strategy if necessary.
  • Integrate with CI/CD: Include API validation in your CI/CD pipeline to catch errors early and ensure that changes do not break the API contract.
  • Test Thoroughly: Write unit and integration tests for your API endpoints to verify that the validation works as expected under various scenarios.

11. FAQs

How do the filter and interceptor that we configured works?

What is the role of the API contract file?



12. Conclusion

Integrating the Atlassian Swagger Request Validator into your Spring Boot project helps catch mistakes early and ensures your API behaves as expected. This simple setup can save you time in debugging and maintenance, making your development process smoother and your application more reliable.

13. Learn More

#

Interested in learning more?

Spring Boot RestControllerAdvice Annotation: Global Exception Handling Made Easy



Add a Comment

Your email address will not be published.