Documenting your API is an essential part for building clean, intuitive and reliable REST APIs! Good documentation can make your API self-explanatory, easy to use, and easy to understand!

In this article, we will cover in-depth how to use Swagger 2 to document a Spring Boot RESTful API.

First, we will implement a simple basic REST API using Spring Boot, JPA/Hibernate, Lombok and MySQL! Then we will integrate Swagger 2 to generate our API docs!

So, let’s get started!

Introduction to SpringFox Swagger2

Swagger is built around OpenAPI specification and has a lot of power when it comes to documenting RESTful APIs!

Over the past few years, Swagger has become one of the most used tools to produce REST APIs documentation! It offers flexibility on several levels and provides both, JSON and UI support!

What about SpringFox ?

Well, SpringFox is the most widely used implementation of Swagger 2.0 specification!

In this tutorial, we are going to use SpringFox implementation to generate our API’s documentation!

Note that, we will be using Swagger, Swagger 2 and SpringFox to refer at the same thing!

Swagger dependencies

To use Swagger2 with Spring Boot REST APIs, you need to add the following dependencies in your pom.xml.

    
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
    
  • springfox-swagger2 offers all the features that can be used to generate JSON API documentation for spring based applications.

  • springfox-swagger-ui provides all the necessary resources required for generating an interactive web interface.

Creating Spring Boot project

In this tutorial, we are going to build a basic RESTful API to manage a list of Posts stored in a MySQL database!

To do that, we are going to use the following technologies:

  • Java8

  • Spring Boot

  • Spring Data

  • JPA/Hibernate

  • Lombok

  • MySQL

  • Swagger2

Our Spring Boot API will expose the following RESTful endpoints:

GET /api/posts Display all Posts.
GET /api/posts/{id}Get single Post by ID.
POST /api/postsCreate new Post resource.
PUT /api/posts/{id}Update Post details.
DELETE /api/posts/{id}Delete Post by ID.

To quickly bootstrap our Spring Boot project, we are going to lean on Spring Initializr!

Open https://start.spring.io/ and fill out all the required details!

Add Spring Web, Lombok, Spring Data JPA dependencies to your project and you are ALL set!

Do not forget to add Swagger2 dependencies to pom.xml file of the generated project!

I assure that you are familiar with how to create a RESTful API with Spring Boot!

So, I’m not going to explain all the technical details because I already did that in a previous post!

pom.xml

    
        <?xml version="1.0" encoding="UTF-8"?>
        <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
            <modelVersion>4.0.0</modelVersion>
            <parent>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>2.2.6.RELEASE</version>
                <relativePath/> <!-- lookup parent from repository -->
            </parent>
            <groupId>com.restapi</groupId>
            <artifactId>msposts</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <name>msposts</name>
            <description>Posts Spring Boot Project</description>

            <properties>
                <java.version>1.8</java.version>
            </properties>

            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-data-jpa</artifactId>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </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>io.springfox</groupId>
                    <artifactId>springfox-swagger2</artifactId>
                    <version>2.9.2</version>
                </dependency>
                <dependency>
                    <groupId>io.springfox</groupId>
                    <artifactId>springfox-swagger-ui</artifactId>
                    <version>2.9.2</version>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-test</artifactId>
                    <scope>test</scope>
                    <exclusions>
                        <exclusion>
                            <groupId>org.junit.vintage</groupId>
                            <artifactId>junit-vintage-engine</artifactId>
                        </exclusion>
                    </exclusions>
                </dependency>
            </dependencies>

            <build>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                    </plugin>
                </plugins>
            </build>
        </project>
    

Configure MySQL dataSource

    
        ## MySQL DataSource
        spring.datasource.url=jdbc:mysql://localhost:3306/postsdb?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC
        spring.datasource.username=root
        spring.datasource.password=admin@2020@
        ## JPA/Hibernate
        spring.jpa.hibernate.ddl-auto=update
        ## Server Port
        server.port=9009
    

Create JPA Entity class

    
        package com.restapi.msposts.models;
        import java.io.Serializable;
        import java.time.LocalDate;
        import javax.persistence.Entity;
        import javax.persistence.GeneratedValue;
        import javax.persistence.GenerationType;
        import javax.persistence.Id;
        import lombok.Getter;
        import lombok.NoArgsConstructor;
        import lombok.Setter;
        import lombok.experimental.Accessors;

        @Entity
        @Getter 
        @Setter 
        @Accessors(chain = true)
        @NoArgsConstructor
        public class Post implements Serializable{
            private static final long serialVersionUID = 1L;
            
            @Id
            @GeneratedValue(strategy=GenerationType.IDENTITY)
            private int id;
            private String title;
            private String content;
            private LocalDate publishdate;
        }
    

Create Post DTO class

    
        package com.restapi.msposts.dtos;
        import java.time.LocalDate;
        import javax.validation.constraints.FutureOrPresent;
        import javax.validation.constraints.NotBlank;
        import org.springframework.format.annotation.DateTimeFormat;
        import org.springframework.format.annotation.DateTimeFormat.ISO;
        import lombok.Getter;
        import lombok.NoArgsConstructor;
        import lombok.Setter;
        import lombok.experimental.Accessors;

        @Getter 
        @Setter 
        @Accessors(chain = true)
        @NoArgsConstructor
        public class PostDTO {
            @NotBlank
            private String title;
            @NotBlank
            private String content;
            @DateTimeFormat(iso = ISO.DATE)
            @FutureOrPresent
            private LocalDate publishdate;
        }
    

Create Post DTO/Entity mapper class

    
        package com.restapi.msposts.mappers;
        import com.restapi.msposts.dtos.PostDTO;
        import com.restapi.msposts.models.Post;

        public class PostMapper {
            public static Post postDtoTopost(PostDTO postdto) {
                return new Post()
                        .setTitle(postdto.getTitle())
                        .setContent(postdto.getContent())
                        .setPublishdate(postdto.getPublishdate());
            }
            public static PostDTO postToPost(Post post) {
                    return new PostDTO()
                            .setTitle(post.getTitle())
                            .setContent(post.getContent())
                            .setPublishdate(post.getPublishdate());
            }
        }
    

Create JPA repository interface

    
        package com.restapi.msposts.repositories;
        import org.springframework.data.jpa.repository.JpaRepository;
        import com.restapi.msposts.models.Post;
        
        @Repository
        public interface PostRepository extends JpaRepository<Post, Integer> {
        }
    

Create Spring controller

    
        package com.restapi.msposts.controllers;

        import java.net.URI;
        import java.util.List;
        import javax.validation.Valid;
        import javax.validation.constraints.Min;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.http.ResponseEntity;
        import org.springframework.validation.annotation.Validated;
        import org.springframework.web.bind.annotation.DeleteMapping;
        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.PutMapping;
        import org.springframework.web.bind.annotation.RequestBody;
        import org.springframework.web.bind.annotation.RequestMapping;
        import org.springframework.web.bind.annotation.RestController;
        import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
        import com.restapi.msposts.dtos.PostDTO;
        import com.restapi.msposts.exceptions.PostNotFoundException;
        import com.restapi.msposts.mappers.PostMapper;
        import com.restapi.msposts.models.Post;
        import com.restapi.msposts.repositories.PostRepository;

        @RestController
        @RequestMapping("/api")
        @Validated
        public class PostController {
            @Autowired
            private PostRepository postRepository;
    
            // GET ALL POSTS
            @GetMapping(value="/posts")
            List getAll(){
                return postRepository.findAll();
            }
            // GET SINGLE POST BY ID
            @GetMapping(value="/posts/{id}")
            ResponseEntity getById(@PathVariable("id") @Min(1) int id) {
                Post usr = postRepository.findById(id)
                           .orElseThrow(()->new PostNotFoundException("Post with ID : "+id+" Not Found!"));
                return ResponseEntity.ok().body(usr);
            }
            // CREATE NEW POST
            @PostMapping(value="/posts")
            ResponseEntity create( @Valid @RequestBody PostDTO postdto) {
                Post post = PostMapper.postDtoTopost(postdto);
                Post addeduser = postRepository.save(post);
                URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                                    .path("/{id}")
                                    .buildAndExpand(addeduser.getId())
                                    .toUri();
                return ResponseEntity.created(location).build();
            }
            // UPDATE ONE POST DETAILS
            @PutMapping(value="/posts/{id}")
            ResponseEntity update(@PathVariable("id")  @Min(1) int id, @Valid @RequestBody PostDTO postdto) {
                Post ppost = postRepository.findById(id)
                       .orElseThrow(()->new PostNotFoundException("Post with ID : "+id+" Not Found!"));
                Post newpost = PostMapper.postDtoTopost(postdto);
                newpost.setId(ppost.getId());
                postRepository.save(newpost);
                return ResponseEntity.ok().body(newpost);
            }
            // DELETE POST BY ID
            @DeleteMapping(value="/posts/{id}")
            ResponseEntity delete(@PathVariable("id") @Min(1) int id) {
                Post post = postRepository.findById(id)
                        .orElseThrow(()->new PostNotFoundException("Post with ID : "+id+" Not Found!"));
                postRepository.deleteById(post.getId());
                return ResponseEntity.ok().body("Post deleted with success!");  
            }
        }
    

Handle validation errors and exceptions of our REST API

We are not going to dive deep into the exception handling mechanism! I already wrote a complete guide about how to handle REST API exceptions properly, feel free to check it: https://devwithus.com/exception-handling-spring-boot-rest-api/

In this section, we will include only the necessary code required to handle the following exceptions:

  • PostNotFoundException, raised when the requested Post is not found.

  • MethodArgumentNotValidException, thrown when a handler method parameter annotated with @Valid is not valid.

  • ConstraintViolationException occurs when @Validated fails validation.

PostNotFoundException custom class

    
        package com.restapi.msposts.exceptions;

        public class PostNotFoundException extends RuntimeException {
            public PostNotFoundException(String message) {
                super(message);
            }
        }
    

ApiError class

    
        import java.time.LocalDateTime;
        import java.util.List;
        import com.fasterxml.jackson.annotation.JsonFormat;
        import lombok.AllArgsConstructor;
        import lombok.Getter;
        import lombok.Setter;

        @Getter
        @Setter
        @AllArgsConstructor
        public class ApiError {
            @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
            private LocalDateTime timestamp;
            private String message;
            private List<String> errors;
        }
    

GlobalExceptionHandler class

    
        package com.restapi.msposts.exceptions;

        import java.time.LocalDateTime;
        import java.util.ArrayList;
        import java.util.List;
        import java.util.stream.Collectors;
        import javax.validation.ConstraintViolationException;
        import org.springframework.http.HttpHeaders;
        import org.springframework.http.HttpStatus;
        import org.springframework.http.ResponseEntity;
        import org.springframework.web.bind.MethodArgumentNotValidException;
        import org.springframework.web.bind.annotation.ControllerAdvice;
        import org.springframework.web.bind.annotation.ExceptionHandler;
        import org.springframework.web.context.request.WebRequest;
        import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
        import com.restapi.msposts.web.ApiError;

        @ControllerAdvice
        public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
            @Override
            protected ResponseEntity<Object> handleMethodArgumentNotValid(
                    MethodArgumentNotValidException ex,
                    HttpHeaders headers, 
                    HttpStatus status, 
                    WebRequest request) {
                List<String> details = new ArrayList<String>();
                details = ex.getBindingResult()
                            .getFieldErrors()
                            .stream()
                            .map(error -> error.getObjectName()+ " : " +error.getDefaultMessage())
                            .collect(Collectors.toList());
                ApiError err = new ApiError(LocalDateTime.now(),
                        "Validation Errors" ,
                        details);
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(err);
            }
            @ExceptionHandler(ConstraintViolationException.class)
            public ResponseEntity<Object> handleConstraintViolationException(
                    Exception ex, 
                    WebRequest request) {
                List<String> details = new ArrayList<String>();
                details.add(ex.getMessage());
                ApiError err = new ApiError(
                        LocalDateTime.now(), 
                        "Constraint Violation" ,
                        details);
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(err);
            }
            @ExceptionHandler(PostNotFoundException.class)
            public ResponseEntity<Object> handleResourceNotFoundException(
                    PostNotFoundException ex) {
                List<String> details = new ArrayList<String>();
                details.add(ex.getMessage());
                ApiError err = new ApiError(
                        LocalDateTime.now(), 
                        "Resource Not Found" ,
                        details);
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body(err);
            }
        }
    

Integrating Swagger 2 with Spring Boot

SpringFox comes with a handy and great annotation called @EnableSwagger2, the source of ALL Swagger2 magic!

The idea is pretty simple! To enable Swagger 2 integration, you need just to create a new Java @configuration class annotated with @EnableSwagger2!

Swagger basic configuration

Springfox Swagger2 configuration is basically organized around Docket class!

Docket provides us with all the necessary methods we need to easily configure Swagger 2 for our Spring Boot application.

First, let’s define a Docket bean in our configuration class!

    
        package com.restapi.msposts.docs;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        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 SpringFoxSwaggerConfig {
            @Bean
            public Docket api() { 
                return new Docket(DocumentationType.SWAGGER_2)  
                  .select()                                   
                  .apis(RequestHandlerSelectors.any())
                  .paths(PathSelectors.any())  
                  .build();
            }
        }
    

Now, allow me to highlight some of the key elements of our SpringFox configuration class:

  • select() lets us initialize a class called ApiSelectorBuilder which provides handy methods that can be used to customize Swagger configuration.

  • apis() allows us to filter our API documentation based on some conditions! For example, you can use this method to hide the documentation of a private part of your API.

  • RequestHandlerSelectors is a predicate (introduced since Java 8) which returns TRUE or FALSE. We used any which always return TRUE. In simple words, RequestHandlerSelectors.any() tells Swagger to document all classes of all packages!

  • paths() provides, with the help of PathSelectors, another way to control how our API docs will be produced by Swagger2! For example, we can ask Swagger to document only handler methods whose URIs start with /api.

Now, let’s run our application and hit the follwing URL : http://localhost:9009/v2/api-docs

JSON Swagger documentation of our Spring Boot REST API

Cool! we have got a complete JSON Swagger documentation for our Spring Boot REST API!

Enabling Swagger UI

Swagger 2 provides a nice built-in web interface that allows API consumers to access Swagger-generated documentation in a more elegant and interactive way.

In order to enable Swagger UI, you need to add springfox-swagger-ui dependency in your pom.xml file.

Open http://localhost:9009/swagger-ui.html in your favorite browser and tell me what do you see!

Looks AMAZING, right? A human-readable colorful HTML page! I’m pretty sure you are happy with what you are seeing right now :)

Swagger UI API documentation

Advanced Swagger2 configuration

Exposing your entire API docs is not always a good idea! So, in this chapter, we are going to discuss how to customize and restrict the logic of generating our REST API documentation!

We will use RequestHandlerSelectors.basePackage() to include only controllers belonging to one particular package - only com.restapi.msposts.controllers package where our controllers live.

We can also tell Swagger to document only RESTful endpoints that are prefixed with /api! We can do that using PathSelectors.ant().

    
        @Bean
        public Docket api() { 
            return new Docket(DocumentationType.SWAGGER_2)  
                .select() 
                .apis(RequestHandlerSelectors.basePackage("com.restapi.msposts.controllers"))
                .paths(PathSelectors.ant("/api"))  
                .build();
        }
    

Custom API informations

It is always a good practice to add general contact information to your API documentation!

To do so, SpringFox Swagger2 comes with ApiInfo class to help you provide relevant details about your API such as title, website, email, author …

    
        @Bean
        public Docket api() { 
            return new Docket(DocumentationType.SWAGGER_2)  
              .select() 
              .apis(RequestHandlerSelectors.basePackage("com.restapi.msposts.controllers"))
              .paths(PathSelectors.ant("/api"))  
              .build()
              .apiInfo(metaData());
        }
        
        private ApiInfo metaData() {
            ApiInfo apiInfo = new ApiInfo(
                    "MS Posts : Spring Boot REST API",
                    "Spring Boot REST API for Posts management",
                    "1.0",
                    "Terms of service",
                    new Contact("Azhwani", "https://devwithus.com", "azhwani@devwithus.com"),
                    "Apache License Version 2.0",
                    "https://www.apache.org/licenses/LICENSE-2.0",new ArrayList<VendorExtension>());
            return apiInfo;
        }
    

Now, let’s restart our application and see what will happen!

Add ApiInfo to API documentation

As you can see, our API documentation is populated with all the information we have already specified using ApiInfo!

Documenting Spring Boot API using Swagger 2 annotations

In this section, we are going to talk about some of the important annotations provided by SpringFox Swagger2 to enhance our Spring Boot API documentation.

Document controllers using Swagger 2

SpringFox provides many built-in annotations that you can use to decorate your RESTful controllers or their handler methods!

Each annotation lets us add some extra metadata and describe the purpose of the marked element!

  • @Api allows us to describe the purpose of a particular controller.

  • @ApiOperation lets you add a short description to a specific handler method.

  • @ApiParam is used to specify some details about the marked request parameter.

  • @ApiResponse is used to document HTTP responses.

The following is the complete code of PostController that includes SpringFox annotations:

    
        @RestController
        @RequestMapping("/api")
        @Validated
        @Api(value = "Post MicroService", description = "MicroService For Posts Management")
        public class PostController {
            
            @Autowired
            private PostRepository postRepository;
            
            // GET ALL POSTS
            @ApiOperation(value = "Display all available Posts", response = Iterable.class)
            @GetMapping(value="/posts")
            List getAll(){
                return postRepository.findAll();
            }
            // GET SINGLE POST BY ID
            @ApiOperation(value = "View Post details by ID", response = Post.class)
            @GetMapping(value="/posts/{id}")
            ResponseEntity getById(@ApiParam(value = "Post ID") @PathVariable("id") @Min(1) int id) {
                Post usr = postRepository.findById(id)
                           .orElseThrow(()->new PostNotFoundException("Post with ID : "+id+" Not Found!"));
                return ResponseEntity.ok().body(usr);
            }
            // CREATE NEW POST
            @ApiOperation(value = "Create new Post")
            @PostMapping(value="/posts")
            ResponseEntity create(@ApiParam(value = "Post DTO instance") @Valid @RequestBody PostDTO postdto) {
                Post post = PostMapper.postDtoTopost(postdto);
                Post addeduser = postRepository.save(post);
                URI location = ServletUriComponentsBuilder.fromCurrentRequest()
                                    .path("/{id}")
                                    .buildAndExpand(addeduser.getId())
                                    .toUri();
                return ResponseEntity.created(location).build();
            }
            // UPDATE ONE POST DETAILS
            @ApiOperation(value = "Update Post details by ID")
            @PutMapping(value="/posts/{id}")
            ResponseEntity update(@ApiParam(value = "Post ID") @PathVariable("id")  @Min(1) int id, @ApiParam(value = "Post DTO instance") @Valid @RequestBody PostDTO postdto) {
                Post ppost = postRepository.findById(id)
                       .orElseThrow(()->new PostNotFoundException("Post with ID : "+id+" Not Found!"));
                Post newpost = PostMapper.postDtoTopost(postdto);
                newpost.setId(ppost.getId());
                postRepository.save(newpost);
                return ResponseEntity.ok().body(newpost);
            }
            // DELETE POST BY ID
            @ApiOperation(value = "Delete Post by ID")
            @DeleteMapping(value="/posts/{id}")
            ResponseEntity delete(@ApiParam(value = "Post ID") @PathVariable("id") @Min(1) int id) {
                Post post = postRepository.findById(id)
                        .orElseThrow(()->new PostNotFoundException("Post with ID : "+id+" Not Found!"));
                postRepository.deleteById(post.getId());
                return ResponseEntity.ok().body("Post deleted with success!");  
            }
        }
    

annotate Spring Controller with Swagger annotations

Swagger annotations for domain models

SpringFox offers also several annotations that can be used to describe and add additional information about your domain models.

  • @ApiModel is used to describe your domain model.

  • @ApiModelProperty allows us to include some extra details about specific properties.

Let’s try to use @ApiModel and @ApiModelProperty annotations in our PostDTO class:

    
        @ApiModel(description = "Post Data Transfer Object class")
        @Getter 
        @Setter 
        @Accessors(chain = true)
        @NoArgsConstructor
        public class PostDTO {
            @ApiModelProperty(notes = "Post Title", required = true, example = "Swagger Annotations",  position = 0)
            @NotBlank
            private String title;
            @ApiModelProperty(notes = "Post Content", required = true, example = "Post about how to document a Spring Boot REST API with Swagger ...", position = 1)
            @NotBlank
            private String content;
            @ApiModelProperty(notes = "Post Publish Date",example = "2020-05-09", position = 2)
            @DateTimeFormat(iso = ISO.DATE)
            @FutureOrPresent
            private LocalDate publishdate;
        }  
    

annotate Domain Models with Swagger2 annotations

Swagger2 and Beans Validation API

Unfortunately, SpringFox Swagger2 cannot detect JSR-303 annotations automatically! However, the good news is that, there is a workaround!

In order to make SpringFox include JSR-303 annotations in our API docs, we need to do two things:

  • include springfox-bean-validators dependency in pom.xml file:
    
        <dependency>
           <groupId>io.springfox</groupId>
           <artifactId>springfox-bean-validators</artifactId>
           <version>2.9.2</version>
        </dependency>
    
  • import BeanValidatorPluginsConfiguration into SpringFox Swagger2 configuration class:
    
        @Configuration
        @EnableSwagger2
        @Import(BeanValidatorPluginsConfiguration.class)
        public class SpringFoxSwaggerConfig {
            ...
        } 
    

Now, let’s decorate title field of our PostDTO class with @Min annotation!

    
        @Size(max = 50)
        private String title;
    

Bean validation and Swagger

Conclusion

That’s all folks! You have learned how to document your Spring Boot REST API with SpringFox Swagger2!

You can find the complete code of this tutorial in our Github repository: https://github.com/devwithus/msposts

Take care and stay SAFE!