My simple library

..of useful code



Chapters

Creating a REST-API in Java

Building and consuming RESTful web services in Java

REST API Fundamentals

What is REST?

REST (Representational State Transfer) is an architectural style for designing networked applications. Key principles:

  • Client-Server: Separation of concerns
  • Stateless: Each request contains all necessary information
  • Cacheable: Responses must define themselves as cacheable or not
  • Uniform Interface: Resources are identified in requests
  • Layered System: Intermediary servers can improve scalability

HTTP Methods

Method Description Idempotent
GET Retrieve a resource Yes
POST Create a new resource No
PUT Update an existing resource (full update) Yes
PATCH Partially update a resource No
DELETE Remove a resource Yes

Creating a REST API with Spring Boot

Project Setup

<!-- pom.xml dependencies -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

Entity Class

// Product.java
@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    private String description;
    private double price;

    // Constructors, getters and setters
}

Repository Interface

// ProductRepository.java
public interface ProductRepository extends JpaRepository<Product, Long> {
    List<Product> findByNameContaining(String name);
}

REST Controller

Basic Controller

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

    @Autowired
    private ProductRepository productRepository;

    @GetMapping
    public List<Product> getAllProducts() {
        return productRepository.findAll();
    }

    @GetMapping("/{id}")
    public ResponseEntity<Product> getProductById(@PathVariable Long id) {
        return productRepository.findById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    public Product createProduct(@Valid @RequestBody Product product) {
        return productRepository.save(product);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Product> updateProduct(
            @PathVariable Long id,
            @Valid @RequestBody Product productDetails) {

        return productRepository.findById(id)
            .map(product -> {
                product.setName(productDetails.getName());
                product.setDescription(productDetails.getDescription());
                product.setPrice(productDetails.getPrice());
                Product updatedProduct = productRepository.save(product);
                return ResponseEntity.ok(updatedProduct);
            })
            .orElse(ResponseEntity.notFound().build());
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<?> deleteProduct(@PathVariable Long id) {
        return productRepository.findById(id)
            .map(product -> {
                productRepository.delete(product);
                return ResponseEntity.ok().build();
            })
            .orElse(ResponseEntity.notFound().build());
    }
}
Key Annotations
  • @RestController: Combines @Controller and @ResponseBody
  • @RequestMapping: Maps web requests to handler methods
  • @GetMapping, @PostMapping, etc.: Shortcut for request method mapping
  • @PathVariable: Binds a method parameter to a URI template variable
  • @RequestBody: Binds HTTP request body to a method parameter

Advanced Features

Exception Handling

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleResourceNotFound(
            ResourceNotFoundException ex) {

        ErrorResponse error = new ErrorResponse(
            "NOT_FOUND",
            ex.getMessage(),
            HttpStatus.NOT_FOUND.value());

        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationErrors(
            MethodArgumentNotValidException ex) {

        List<String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(FieldError::getDefaultMessage)
            .collect(Collectors.toList());

        ErrorResponse error = new ErrorResponse(
            "VALIDATION_FAILED",
            "Validation failed",
            HttpStatus.BAD_REQUEST.value(),
            errors);

        return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
    }
}

DTOs (Data Transfer Objects)

// ProductDTO.java
public class ProductDTO {
    private String name;
    private String description;
    private double price;

    // Constructors, getters and setters

    public static ProductDTO fromEntity(Product product) {
        ProductDTO dto = new ProductDTO();
        dto.setName(product.getName());
        dto.setDescription(product.getDescription());
        dto.setPrice(product.getPrice());
        return dto;
    }

    public Product toEntity() {
        Product product = new Product();
        product.setName(this.getName());
        product.setDescription(this.getDescription());
        product.setPrice(this.getPrice());
        return product;
    }
}

API Documentation with Swagger

<!-- Add to pom.xml -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>
// SwaggerConfig.java
@Configuration
public class SwaggerConfig {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
            .select()
            .apis(RequestHandlerSelectors.any())
            .paths(PathSelectors.any())
            .build()
            .apiInfo(apiInfo());
    }

    private ApiInfo apiInfo() {
        return new ApiInfo(
            "Product API",
            "API for managing products",
            "1.0",
            "Terms of service",
            new Contact("Your Name", "www.example.com", "contact@example.com"),
            "License",
            "License URL",
            Collections.emptyList());
    }
}

After adding Swagger, access the API documentation at:

  • http://localhost:8080/swagger-ui/ - Swagger UI
  • http://localhost:8080/v2/api-docs - Raw API docs

Testing the API

Unit Testing with JUnit and Mockito

@ExtendWith(MockitoExtension.class)
class ProductControllerTest {

    @Mock
    private ProductRepository productRepository;

    @InjectMocks
    private ProductController productController;

    @Test
    void getProductById_shouldReturnProduct() {
        // Arrange
        Product product = new Product(1L, "Test", "Desc", 9.99);
        when(productRepository.findById(1L)).thenReturn(Optional.of(product));

        // Act
        ResponseEntity<Product> response = productController.getProductById(1L);

        // Assert
        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertEquals("Test", response.getBody().getName());
    }

    @Test
    void getProductById_shouldReturnNotFound() {
        when(productRepository.findById(1L)).thenReturn(Optional.empty());

        ResponseEntity<Product> response = productController.getProductById(1L);

        assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
    }
}

Consuming REST APIs in Java

Using HttpURLConnection

// GET request example
URL url = new URL("http://localhost:8080/api/products/1");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");

int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
    BufferedReader in = new BufferedReader(
        new InputStreamReader(conn.getInputStream()));
    String inputLine;
    StringBuilder response = new StringBuilder();

    while ((inputLine = in.readLine()) != null) {
        response.append(inputLine);
    }
    in.close();

    // Parse JSON response
    ObjectMapper mapper = new ObjectMapper();
    Product product = mapper.readValue(response.toString(), Product.class);
    System.out.println(product.getName());
}

Using Spring's RestTemplate

// GET request
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/api/products/1";

ResponseEntity<Product> response =
    restTemplate.getForEntity(url, Product.class);

if (response.getStatusCode() == HttpStatus.OK) {
    Product product = response.getBody();
    System.out.println(product.getName());
}

// POST request
Product newProduct = new Product(null, "New", "Desc", 19.99);
ResponseEntity<Product> postResponse =
    restTemplate.postForEntity(url, newProduct, Product.class);

Using WebClient (Reactive)

WebClient webClient = WebClient.create("http://localhost:8080");

// GET request
Mono<Product> productMono = webClient.get()
    .uri("/api/products/1")
    .retrieve()
    .bodyToMono(Product.class);

productMono.subscribe(product ->
    System.out.println(product.getName()));

// POST request
Product newProduct = new Product(null, "New", "Desc", 19.99);
Mono<Product> createdProduct = webClient.post()
    .uri("/api/products")
    .contentType(MediaType.APPLICATION_JSON)
    .bodyValue(newProduct)
    .retrieve()
    .bodyToMono(Product.class);

Best Practices

  • Use proper HTTP status codes: 200 for success, 201 for created, 400 for bad requests, etc.
  • Implement HATEOAS: Include links to related resources
  • Version your API: Include version in the URL (/v1/products) or headers
  • Use pagination: For large collections, implement page and size parameters
  • Secure your API: Use HTTPS, implement authentication (JWT, OAuth2)
  • Validate input: Use Bean Validation (@Valid) to validate request bodies
  • Document your API: Use Swagger/OpenAPI for documentation
  • Handle errors consistently: Return standardized error responses
  • Use DTOs: Don't expose your entities directly
  • Implement caching: Use ETags and Cache-Control headers