Consuming a REST API with Java: A Complete Command-Line CRUD Tutorial

Consuming a REST API with Java: A Complete Command-Line CRUD Tutorial

In this comprehensive tutorial, we’ll learn how to consume a REST API using Java entirely from the command line. We’ll build a complete project covering all CRUD operations using the free JSONPlaceholder API. Let’s dive in!

Project Overview

Folder Structure

java-api-client/
│
├── lib/
│   └── gson-2.10.1.jar      # External JSON library
│
├── src/
│   ├── Post.java            # Model class
│   ├── ApiClient.java       # HTTP client utility
│   ├── JsonProcessor.java   # JSON handling
│   └── ApiConsumerApp.java  # Main application
│
└── compile-and-run.sh       # Script to compile and run

Prerequisites

  • Java 11 or higher
  • Basic Java knowledge
  • Command line/terminal access
  • Text editor (VS Code, Vim, Nano, etc.)

Step 1: Project Setup

First, create the project structure:

# Create main project directory
mkdir java-api-client
cd java-api-client

# Create all necessary directories
mkdir -p src lib

# Create a compilation script
touch compile-and-run.sh
chmod +x compile-and-run.sh

Step 2: Download Required Library

We’ll use Google’s Gson library for JSON processing. Download it to the lib folder:

cd lib
curl -L -o gson-2.10.1.jar https://repo1.maven.org/maven2/com/google/code/gson/gson/2.10.1.jar
cd ..

Step 3: Create the Model Class (Post.java)

Create src/Post.java:

package com.apiclient;

public class Post {
    private int userId;
    private int id;
    private String title;
    private String body;

    // Default constructor
    public Post() {}

    // Constructor with parameters
    public Post(int userId, String title, String body) {
        this.userId = userId;
        this.title = title;
        this.body = body;
    }

    // Getters and setters
    public int getUserId() { 
        return userId; 
    }

    public void setUserId(int userId) { 
        this.userId = userId; 
    }

    public int getId() { 
        return id; 
    }

    public void setId(int id) { 
        this.id = id; 
    }

    public String getTitle() { 
        return title; 
    }

    public void setTitle(String title) { 
        this.title = title; 
    }

    public String getBody() { 
        return body; 
    }

    public void setBody(String body) { 
        this.body = body; 
    }

    @Override
    public String toString() {
        return String.format(
            "Post #%d\nUser ID: %d\nTitle: %s\nBody: %s\n%s\n",
            id, userId, title, body, "-".repeat(50)
        );
    }
}

Step 4: Create the HTTP Client (ApiClient.java)

Create src/ApiClient.java:

package com.apiclient;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class ApiClient {
    private static final String BASE_URL = "https://jsonplaceholder.typicode.com";
    private static final HttpClient httpClient = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_2)
            .connectTimeout(Duration.ofSeconds(10))
            .build();

    /**
     * Send a GET request to the specified endpoint
     */
    public static String get(String endpoint) throws IOException, InterruptedException {
        HttpRequest request = HttpRequest.newBuilder()
                .GET()
                .uri(URI.create(BASE_URL + endpoint))
                .header("Accept", "application/json")
                .build();

        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        logRequest("GET", endpoint, response.statusCode());
        return response.body();
    }

    /**
     * Send a POST request with JSON body
     */
    public static String post(String endpoint, String jsonBody) throws IOException, InterruptedException {
        HttpRequest request = HttpRequest.newBuilder()
                .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
                .uri(URI.create(BASE_URL + endpoint))
                .header("Content-Type", "application/json")
                .header("Accept", "application/json")
                .build();

        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        logRequest("POST", endpoint, response.statusCode());
        return response.body();
    }

    /**
     * Send a PUT request with JSON body
     */
    public static String put(String endpoint, String jsonBody) throws IOException, InterruptedException {
        HttpRequest request = HttpRequest.newBuilder()
                .PUT(HttpRequest.BodyPublishers.ofString(jsonBody))
                .uri(URI.create(BASE_URL + endpoint))
                .header("Content-Type", "application/json")
                .header("Accept", "application/json")
                .build();

        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        logRequest("PUT", endpoint, response.statusCode());
        return response.body();
    }

    /**
     * Send a PATCH request with JSON body
     */
    public static String patch(String endpoint, String jsonBody) throws IOException, InterruptedException {
        HttpRequest request = HttpRequest.newBuilder()
                .method("PATCH", HttpRequest.BodyPublishers.ofString(jsonBody))
                .uri(URI.create(BASE_URL + endpoint))
                .header("Content-Type", "application/json")
                .header("Accept", "application/json")
                .build();

        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        logRequest("PATCH", endpoint, response.statusCode());
        return response.body();
    }

    /**
     * Send a DELETE request
     */
    public static String delete(String endpoint) throws IOException, InterruptedException {
        HttpRequest request = HttpRequest.newBuilder()
                .DELETE()
                .uri(URI.create(BASE_URL + endpoint))
                .header("Accept", "application/json")
                .build();

        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        logRequest("DELETE", endpoint, response.statusCode());
        return response.body();
    }

    private static void logRequest(String method, String endpoint, int statusCode) {
        System.out.printf("[%s] %s -> Status: %d%n", method, endpoint, statusCode);
    }
}

Step 5: Create JSON Processor (JsonProcessor.java)

Create src/JsonProcessor.java:

package com.apiclient;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;
import java.util.ArrayList;

public class JsonProcessor {
    private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();

    /**
     * Convert a single Post object to JSON string
     */
    public static String toJson(Post post) {
        return gson.toJson(post);
    }

    /**
     * Convert JSON string to a single Post object
     */
    public static Post fromJson(String json) {
        return gson.fromJson(json, Post.class);
    }

    /**
     * Convert JSON array string to List of Post objects
     */
    public static List<Post> fromJsonArray(String json) {
        Type listType = new TypeToken<ArrayList<Post>>(){}.getType();
        return gson.fromJson(json, listType);
    }

    /**
     * Convert List of Post objects to JSON array string
     */
    public static String toJsonArray(List<Post> posts) {
        return gson.toJson(posts);
    }

    /**
     * Pretty print JSON string
     */
    public static String prettyPrint(String json) {
        Object obj = gson.fromJson(json, Object.class);
        return gson.toJson(obj);
    }
}

Step 6: Create Main Application (ApiConsumerApp.java)

Create src/ApiConsumerApp.java:

package com.apiclient;

import java.io.IOException;
import java.util.List;
import java.util.Scanner;

public class ApiConsumerApp {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.println("=========================================");
        System.out.println("    JSONPlaceholder API Client");
        System.out.println("=========================================");

        while (true) {
            displayMenu();
            int choice = getIntInput(scanner, "Choose an option (1-7): ");

            try {
                switch (choice) {
                    case 1:
                        getAllPosts();
                        break;
                    case 2:
                        int getPostId = getIntInput(scanner, "Enter post ID to retrieve: ");
                        getPostById(getPostId);
                        break;
                    case 3:
                        createNewPost(scanner);
                        break;
                    case 4:
                        int putPostId = getIntInput(scanner, "Enter post ID to update: ");
                        updatePost(putPostId, scanner);
                        break;
                    case 5:
                        int patchPostId = getIntInput(scanner, "Enter post ID to partially update: ");
                        partiallyUpdatePost(patchPostId, scanner);
                        break;
                    case 6:
                        int deletePostId = getIntInput(scanner, "Enter post ID to delete: ");
                        deletePost(deletePostId);
                        break;
                    case 7:
                        System.out.println("\nThank you for using the API Client. Goodbye!");
                        scanner.close();
                        return;
                    default:
                        System.out.println("Invalid choice. Please enter a number between 1 and 7.");
                }
            } catch (IOException | InterruptedException e) {
                System.err.println("Error: " + e.getMessage());
                e.printStackTrace();
            }

            System.out.println("\nPress Enter to continue...");
            scanner.nextLine();
        }
    }

    private static void displayMenu() {
        System.out.println("\n=== MAIN MENU ===");
        System.out.println("1. Get all posts");
        System.out.println("2. Get a specific post");
        System.out.println("3. Create a new post");
        System.out.println("4. Update a post (PUT - full update)");
        System.out.println("5. Partially update a post (PATCH)");
        System.out.println("6. Delete a post");
        System.out.println("7. Exit");
    }

    private static int getIntInput(Scanner scanner, String prompt) {
        while (true) {
            System.out.print(prompt);
            try {
                int value = scanner.nextInt();
                scanner.nextLine(); // Consume newline
                return value;
            } catch (Exception e) {
                System.out.println("Invalid input. Please enter a valid number.");
                scanner.nextLine(); // Clear invalid input
            }
        }
    }

    private static String getStringInput(Scanner scanner, String prompt) {
        System.out.print(prompt);
        return scanner.nextLine();
    }

    // CRUD Operations Implementation

    /**
     * 1. GET all posts (Read operation)
     */
    private static void getAllPosts() throws IOException, InterruptedException {
        System.out.println("\n--- Fetching all posts ---");
        String response = ApiClient.get("/posts");
        List<Post> posts = JsonProcessor.fromJsonArray(response);

        System.out.println("\nTotal posts retrieved: " + posts.size());
        if (!posts.isEmpty()) {
            System.out.println("\nFirst 5 posts:");
            for (int i = 0; i < Math.min(5, posts.size()); i++) {
                System.out.println(posts.get(i));
            }
            if (posts.size() > 5) {
                System.out.println("... and " + (posts.size() - 5) + " more posts.");
            }
        }
    }

    /**
     * 2. GET a specific post by ID (Read operation)
     */
    private static void getPostById(int id) throws IOException, InterruptedException {
        System.out.println("\n--- Fetching post #" + id + " ---");
        String response = ApiClient.get("/posts/" + id);

        if (response.contains("\"id\":")) {
            Post post = JsonProcessor.fromJson(response);
            System.out.println("\n" + post);
        } else {
            System.out.println("Post not found or error occurred.");
            System.out.println("Response: " + JsonProcessor.prettyPrint(response));
        }
    }

    /**
     * 3. POST a new post (Create operation)
     */
    private static void createNewPost(Scanner scanner) throws IOException, InterruptedException {
        System.out.println("\n--- Creating a new post ---");

        int userId = getIntInput(scanner, "Enter user ID: ");
        String title = getStringInput(scanner, "Enter title: ");
        String body = getStringInput(scanner, "Enter body: ");

        Post newPost = new Post(userId, title, body);
        String jsonBody = JsonProcessor.toJson(newPost);

        System.out.println("\nSending POST request with data:");
        System.out.println(JsonProcessor.prettyPrint(jsonBody));

        String response = ApiClient.post("/posts", jsonBody);

        System.out.println("\nResponse from server:");
        System.out.println(JsonProcessor.prettyPrint(response));

        Post createdPost = JsonProcessor.fromJson(response);
        System.out.println("\nCreated post (with generated ID):");
        System.out.println(createdPost);
    }

    /**
     * 4. PUT to update an entire post (Update operation)
     */
    private static void updatePost(int id, Scanner scanner) throws IOException, InterruptedException {
        System.out.println("\n--- Fully updating post #" + id + " ---");

        // First, get the existing post to show what we're updating
        try {
            String existing = ApiClient.get("/posts/" + id);
            Post existingPost = JsonProcessor.fromJson(existing);
            System.out.println("\nCurrent post data:");
            System.out.println(existingPost);
        } catch (Exception e) {
            System.out.println("Could not retrieve existing post. Continuing with update...");
        }

        int userId = getIntInput(scanner, "Enter new user ID: ");
        String title = getStringInput(scanner, "Enter new title: ");
        String body = getStringInput(scanner, "Enter new body: ");

        Post updatedPost = new Post(userId, title, body);
        updatedPost.setId(id); // Set the ID for the update

        String jsonBody = JsonProcessor.toJson(updatedPost);

        System.out.println("\nSending PUT request with data:");
        System.out.println(JsonProcessor.prettyPrint(jsonBody));

        String response = ApiClient.put("/posts/" + id, jsonBody);

        System.out.println("\nResponse from server:");
        System.out.println(JsonProcessor.prettyPrint(response));

        Post resultPost = JsonProcessor.fromJson(response);
        System.out.println("\nUpdated post:");
        System.out.println(resultPost);
    }

    /**
     * 5. PATCH to partially update a post (Update operation)
     */
    private static void partiallyUpdatePost(int id, Scanner scanner) throws IOException, InterruptedException {
        System.out.println("\n--- Partially updating post #" + id + " ---");

        // Show what fields we can update
        System.out.println("Which fields would you like to update?");
        System.out.println("1. Title only");
        System.out.println("2. Body only");
        System.out.println("3. Both title and body");

        int fieldChoice = getIntInput(scanner, "Enter choice (1-3): ");

        String jsonBody = "";

        switch (fieldChoice) {
            case 1:
                String title = getStringInput(scanner, "Enter new title: ");
                jsonBody = String.format("{\"title\": \"%s\"}", title);
                break;
            case 2:
                String body = getStringInput(scanner, "Enter new body: ");
                jsonBody = String.format("{\"body\": \"%s\"}", body);
                break;
            case 3:
                title = getStringInput(scanner, "Enter new title: ");
                body = getStringInput(scanner, "Enter new body: ");
                jsonBody = String.format("{\"title\": \"%s\", \"body\": \"%s\"}", title, body);
                break;
            default:
                System.out.println("Invalid choice. Returning to menu.");
                return;
        }

        System.out.println("\nSending PATCH request with data:");
        System.out.println(JsonProcessor.prettyPrint(jsonBody));

        String response = ApiClient.patch("/posts/" + id, jsonBody);

        System.out.println("\nResponse from server:");
        System.out.println(JsonProcessor.prettyPrint(response));

        Post resultPost = JsonProcessor.fromJson(response);
        System.out.println("\nPartially updated post:");
        System.out.println(resultPost);
    }

    /**
     * 6. DELETE a post (Delete operation)
     */
    private static void deletePost(int id) throws IOException, InterruptedException {
        System.out.println("\n--- Deleting post #" + id + " ---");

        // Confirm deletion
        System.out.print("Are you sure you want to delete post #" + id + "? (yes/no): ");
        Scanner tempScanner = new Scanner(System.in);
        String confirmation = tempScanner.nextLine().toLowerCase();

        if (!confirmation.equals("yes") && !confirmation.equals("y")) {
            System.out.println("Deletion cancelled.");
            return;
        }

        String response = ApiClient.delete("/posts/" + id);

        System.out.println("\nResponse from server:");
        System.out.println(JsonProcessor.prettyPrint(response));

        System.out.println("\nNote: JSONPlaceholder is a fake API, so the post won't be actually deleted.");
        System.out.println("But the DELETE operation was successful (status code 200).");
    }
}

Step 7: Create Compilation and Run Script

Create compile-and-run.sh in the project root:

#!/bin/bash

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

echo -e "${BLUE}=== Java API Client Compilation Script ===${NC}"

# Check if Java is installed
if ! command -v javac &> /dev/null; then
    echo -e "${RED}Error: javac is not installed. Please install Java Development Kit (JDK).${NC}"
    exit 1
fi

# Create output directory if it doesn't exist
mkdir -p out

# Compile all Java files with classpath
echo -e "\n${GREEN}Compiling Java files...${NC}"
javac -cp "lib/*" -d out src/*.java

if [ $? -eq 0 ]; then
    echo -e "${GREEN}Compilation successful!${NC}"

    # Run the application
    echo -e "\n${BLUE}Running API Client...${NC}"
    echo -e "${BLUE}=========================================${NC}"
    java -cp "out:lib/*" com.apiclient.ApiConsumerApp
else
    echo -e "${RED}Compilation failed. Please check the errors above.${NC}"
    exit 1
fi

Step 8: Running the Application

Now let’s run our complete application:

# Make sure you're in the java-api-client directory
./compile-and-run.sh

Step 9: Testing All CRUD Operations

When you run the application, you’ll see a menu with these options:

1. GET all posts (Read)

  • Demonstrates retrieving a collection of resources
  • Shows how to parse JSON arrays

2. GET a specific post (Read)

  • Shows retrieving a single resource by ID
  • Demonstrates error handling for non-existent resources

3. POST a new post (Create)

  • Shows how to send data to create a new resource
  • Demonstrates JSON serialization
  • Example request body:
{
  "userId": 1,
  "title": "My New Post",
  "body": "This is the content of my new post."
}

4. PUT to update a post (Update)

  • Shows full resource replacement
  • Demonstrates the difference between PUT and PATCH

5. PATCH to partially update (Update)

  • Shows partial resource updates
  • More efficient than PUT when only changing some fields

6. DELETE a post (Delete)

  • Shows resource deletion
  • Includes confirmation prompt

Understanding the Code Structure

Key Components:

  1. Post.java – The model/entity class representing our data
  2. ApiClient.java – Handles all HTTP communication using Java 11+ HttpClient
  3. JsonProcessor.java – Manages JSON serialization/deserialization using Gson
  4. ApiConsumerApp.java – Main application with user interface and business logic

HTTP Methods Explained:

  • GET: Retrieve data (safe, idempotent)
  • POST: Create new resource (not idempotent)
  • PUT: Replace entire resource (idempotent)
  • PATCH: Partially update resource (not always idempotent)
  • DELETE: Remove resource (idempotent)

Common Issues and Troubleshooting

  1. Connection Errors: Ensure you have internet access
  2. Compilation Errors: Verify Java version (need 11+ for HttpClient)
  3. JSON Parsing Errors: Check Gson library is in lib/ folder
  4. API Limits: JSONPlaceholder has no rate limits for educational use

Advanced Exercises

  1. Add Error Handling: Implement retry logic for failed requests
  2. Add Logging: Use a logging framework like SLF4J
  3. Add Configuration: Make API URL configurable via properties file
  4. Add Unit Tests: Test each CRUD operation with JUnit
  5. Add More Endpoints: Extend to other resources (users, comments, todos)

Conclusion

You’ve successfully built a complete Java command-line application that performs all CRUD operations against a REST API! This project demonstrates:

  • Java 11+ HttpClient usage
  • JSON processing with Gson
  • Clean separation of concerns
  • Comprehensive error handling
  • Interactive command-line interface

This foundation can be extended to work with any REST API by modifying the model classes and endpoints. The skills learned here are transferable to real-world API integrations.

Final Project Structure Recap:

java-api-client/
├── lib/gson-2.10.1.jar
├── src/Post.java
├── src/ApiClient.java
├── src/JsonProcessor.java
├── src/ApiConsumerApp.java
└── compile-and-run.sh

Happy coding! You can now integrate any REST API into your Java applications.

Posts Carousel

Leave a Comment

Your email address will not be published. Required fields are marked with *

Latest Posts

Most Commented

Featured Videos