1. Introduction

In this quick tutorial, we’ll implement a HTTP DELETE endpoint that accepts a body and then explore multiple ways to send a request to it. Consequently, we’ll use different popular REST client implementations.

2. The Problem

The HTTP specification is ambiguous about whether DELETE requests can include a body, stating that “*content received in a DELETE request has no generally defined semantics.*” This leaves the implementation to define the behavior. While it’s technically possible to include a body in DELETE requests, servers aren’t guaranteed to accept or process it.

We’ll examine how Spring’s RestController can accept and process a @RequestBody in a DELETE request. For simplicity, we’ll create a /delete endpoint that echoes the request body, allowing us to verify that the body is correctly handled:

@RestController
@RequestMapping("/delete")
public class DeleteController {

    @DeleteMapping
    public Body delete(@RequestBody Body body) {
        return body;
    }
}

Our body is a simple POJO:

public class Body {
    private String name;
    private Integer value;

    // standard getters and setters
}

During our tests, we’ll use a simple JSON String in our requests so we can easily match the content returned without additional parsing:

String json = "{\"name\":\"foo\",\"value\":1}"

We’re now ready to explore existing REST client implementations that allow us to send content in our request.

3. Using Spring’s RestTemplate

Our first option is using the popular RestTemplate from Spring. We’ll write a client class that receives a URL in its constructor:

public class SpringTemplateDeleteClient {

    private final String url;
    private RestTemplate client = new RestTemplate();

    public SpringTemplateDeleteClient(String url) {
        this.url = url;
    }

    // ...
}

Since RestTemplate doesn’t provide an overloaded delete() method that accepts a body, we use the more generic exchange() method with HttpMethod.DELETE. Let’s see what it looks like:

public String delete(String json) {
    HttpHeaders headers = new HttpHeaders();
    headers.set("Content-Type", "application/json");

    HttpEntity<String> body = new HttpEntity<>(json, headers);

    ResponseEntity<String> response = client.exchange(
      url, HttpMethod.DELETE, body, String.class);
    return response.getBody();
}

Since our controller returns the body precisely as received, we can assert that it correctly handles a body in a DELETE request:

@Test
void whenRestTemplate_thenDeleteWithBody() {
    SpringTemplateDeleteClient client = new SpringTemplateDeleteClient(url);

    assertEquals(json, client.delete(json));
}

4. Using Core Java Classes

The HttpClient in Java 11 lacks a dedicated delete() method that supports bodies, so we use the generic method(“DELETE”) in HttpRequest.newBuilder().

Let’s create a client with the same overall template, a class constructed with a URL containing a delete() method. We’ll receive a JSON String and construct a BodyPublisher using that method. Ultimately, we’ll return the response body as String:

public class PlainDeleteClient {

    private final String url;
    private HttpClient client = HttpClient.newHttpClient();

    // url constructor

    public String delete(String json) throws Exception {
        BodyPublisher body = HttpRequest.BodyPublishers.ofString(json);

        // ...
        
        HttpResponse<String> response = client.send(
          request, HttpResponse.BodyHandlers.ofString());
        return response.body();
    }
}

For the actual request, we’ll use HttpRequest.newBuilder(), which doesn’t contain a delete() helper that accepts a body. Instead, we’ll use the generic method(“DELETE”):

HttpRequest request = HttpRequest.newBuilder(URI.create(url))
  .header("Content-Type", "application/json")
  .method("DELETE", body)
.build();

Let’s test it:

@Test
void whenPlainHttpClient_thenDeleteWithBody() throws Exception {
    PlainDeleteClient client = new PlainDeleteClient(url);

    assertEquals(json, client.delete(json));
}

5. Using Apache HTTP 4

A popular option is using the Apache HTTP client. With this library, standard implementations like HttpPost extend HttpEntityEnclosingRequestBase, which includes a setEntity() method, allowing us to include a body in the request.

Unfortunately, HttpDelete only extends HttpRequestBase, which doesn’t include a way to define a request entity. So, let’s start by extending HttpEntityEnclosingRequestBase to create a custom HttpDeleteBody, which returns DELETE as the HTTP method. We’ll also include a constructor that accepts a String URL:

public class HttpDeleteBody extends HttpEntityEnclosingRequestBase {

    public HttpDeleteBody(final String uri) {
        super();
        setURI(URI.create(uri));
    }

    @Override
    public String getMethod() {
        return "DELETE";
    }
}

Then, we can instantiate it in our client and call setEntity() to pass our body. Finally, we use it with the client.execute() method:

public class ApacheDeleteClient {

    // url constructor

    public String delete(String json) throws IOException {
        try (CloseableHttpClient client = HttpClients.createDefault()) {
            StringEntity body = new StringEntity(json, ContentType.APPLICATION_JSON);

            HttpDeleteBody delete = new HttpDeleteBody(url);
            delete.setEntity(body);

            CloseableHttpResponse response = client.execute(delete);
            return EntityUtils.toString(response.getEntity());
        }
    }
}

As the name suggests, CloseableHttpClient implements Closeable, so we create it in a try-with-resources block. The framework also includes an EntityUtils class to help us convert the response entity to the desired type.

5.1. Using Apache HTTP 5

In version 5 of the library, the default HttpDelete implementation already contains everything we need to define a request body. Also, executing the request is now asynchronous, so we have to build a HttpClientResponseHandler. Let’s replace it in our previous example to see what it looks like:

HttpDelete delete = new HttpDelete(url);
delete.setEntity(body);

HttpClientResponseHandler handler = response -> {
    try (HttpEntity entity = response.getEntity()) {
        return EntityUtils.toString(entity);
    }
};

return client.execute(delete, handler);

Finally, HttpEntity now also implements Closeable, so we must declare it with try-with-resources.

6. Conclusion

In this article, we explored a few client implementations capable of sending DELETE HTTP requests with bodies. Each has strengths, and the choice depends on our specific requirements, such as dependency preferences.

As always, the source code is available over on GitHub.