Skip to content
Samuel De Oliveira edited this page Jan 12, 2026 · 2 revisions

🌐 HTTP

Make HTTP requests to external APIs with built-in JSON serialization, caching, and connection pooling.


Overview

The HTTP API provides a powerful HTTP client for making REST API calls:

  • RESTful Methods — GET, POST, PUT, DELETE support
  • JSON Serialization — Automatic JSON parsing with Jackson
  • Connection Pooling — Reuse connections with ApiPool
  • Response Caching — Optional caching for GET requests
  • Custom Headers — Add authentication tokens and custom headers
  • Health Checks — Built-in ping() method
  • Timeout Control — Configure request timeouts
  • Type Safety — Generic type support

Table of Contents


Getting Started

Basic Setup

import fr.dreamin.dreamapi.api.http.ApiManager;
import fr.dreamin.dreamapi.api.http.core.ApiPool;

public class MyPlugin extends DreamPlugin {

  @Override
  public void onDreamEnable() {
    // Create and register API client
    ApiManager api = ApiPool.register("myapi", new ApiManager.Builder()
      .baseUrl("https://api.example.com")
      .timeout(5000)
      .build()
    );

    // Test connection
    if (api.ping()) {
      getLogger().info("API is online!");
    }
  }

  @Override
  public void onDreamDisable() {
    // Close all connections
    ApiPool.closeAll();
  }
}

ApiManager — HTTP Client

Create Client

ApiManager api = new ApiManager.Builder()
  .baseUrl("https://api.example.com")
  .timeout(5000)
  .build();

With Custom Headers

ApiManager api = new ApiManager.Builder()
  .baseUrl("https://api.example.com")
  .header("Authorization", "Bearer YOUR_TOKEN")
  .header("User-Agent", "MyPlugin/1.0")
  .timeout(10000)
  .build();

With Caching

ApiManager api = new ApiManager.Builder()
  .baseUrl("https://api.example.com")
  .enableCache(true)
  .cacheTtl(60000)  // Cache for 60 seconds
  .build();

Builder Options

ApiManager api = new ApiManager.Builder()
  .baseUrl("https://api.example.com")           // Required
  .timeout(5000)                                 // Request timeout (ms)
  .header("Authorization", "Bearer token")       // Custom header
  .enableCache(true)                             // Enable response caching
  .cacheTtl(30000)                               // Cache TTL (ms)
  .mapper(customObjectMapper)                    // Custom Jackson mapper
  .build();

Health Check

if (api.ping()) {
  player.sendMessage(
    Component.text("API is reachable", NamedTextColor.GREEN)
  );
} else {
  player.sendMessage(
    Component.text("API is down", NamedTextColor.RED)
  );
}

ApiPool — Connection Pool

The ApiPool manages multiple API clients globally.

Register API

// Register with instance
ApiManager api = ApiPool.register("github", new ApiManager.Builder()
  .baseUrl("https://api.github.com")
  .header("Accept", "application/vnd.github.v3+json")
  .build()
);

// Register with supplier (lazy + health check)
ApiManager api = ApiPool.register("myapi", () -> 
  new ApiManager.Builder()
    .baseUrl("https://api.example.com")
    .build()
);

Retrieve API

// Get by label (throws if not found)
ApiManager api = ApiPool.get("github");

// Find by label (returns Optional)
Optional<ApiManager> optional = ApiPool.find("github");
if (optional.isPresent()) {
  ApiManager api = optional.get();
}

// Check if exists
if (ApiPool.exists("github")) {
  ApiManager api = ApiPool.get("github");
}

Manage APIs

// Get all registered APIs
Collection<ApiManager> all = ApiPool.all();
for (ApiManager api : all) {
  getLogger().info("API: " + api.getBaseUrl());
}

// Unregister single API
ApiPool.unregister("github");

// Close all connections
ApiPool.closeAll();

HTTP Methods

GET Request

// Simple GET
User user = api.get("/users/123", User.class);

player.sendMessage(
  Component.text("User: " + user.getName(), NamedTextColor.GREEN)
);

GET List

import com.fasterxml.jackson.core.type.TypeReference;

// Get list of objects
List<User> users = api.getAll("/users", new TypeReference<List<User>>() {});

for (User user : users) {
  getLogger().info("User: " + user.getName());
}

POST Request

// Create user
String json = "{\"name\":\"John\",\"email\":\"john@example.com\"}";
User newUser = api.post("/users", json, User.class);

player.sendMessage(
  Component.text("Created user: " + newUser.getId(), NamedTextColor.GREEN)
);

PUT Request

// Update user
String json = "{\"name\":\"John Doe\"}";
User updated = api.put("/users/123", json, User.class);

player.sendMessage(
  Component.text("Updated user", NamedTextColor.GREEN)
);

DELETE Request

// Delete user
DeleteResponse response = api.delete("/users/123", DeleteResponse.class);

if (response.isSuccess()) {
  player.sendMessage(
    Component.text("User deleted", NamedTextColor.GREEN)
  );
}

Response Handling

Define Response Models

// Simple model
public class User {
  private int id;
  private String name;
  private String email;

  // Getters and setters
  public int getId() { return id; }
  public void setId(int id) { this.id = id; }

  public String getName() { return name; }
  public void setName(String name) { this.name = name; }

  public String getEmail() { return email; }
  public void setEmail(String email) { this.email = email; }
}

Handle Exceptions

try {
  User user = api.get("/users/123", User.class);
  player.sendMessage(
    Component.text("User: " + user.getName(), NamedTextColor.GREEN)
  );
} catch (ApiManager.ApiRequestException e) {
  player.sendMessage(
    Component.text("API Error: " + e.getMessage(), NamedTextColor.RED)
  );
  getLogger().severe("Status: " + e.getStatusCode());
} catch (Exception e) {
  player.sendMessage(
    Component.text("Request failed", NamedTextColor.RED)
  );
  e.printStackTrace();
}

Async Requests

import org.bukkit.Bukkit;

public void fetchUserAsync(Player player, int userId) {
  Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
    try {
      User user = ApiPool.get("myapi").get("/users/" + userId, User.class);

      // Return to main thread
      Bukkit.getScheduler().runTask(this, () -> {
        player.sendMessage(
          Component.text("User: " + user.getName(), NamedTextColor.GREEN)
        );
      });

    } catch (Exception e) {
      Bukkit.getScheduler().runTask(this, () -> {
        player.sendMessage(
          Component.text("Failed to fetch user", NamedTextColor.RED)
        );
      });
    }
  });
}

Caching

Enable Caching

ApiManager api = new ApiManager.Builder()
  .baseUrl("https://api.example.com")
  .enableCache(true)
  .cacheTtl(60000)  // Cache for 60 seconds
  .build();

How Caching Works

// First request - fetches from API
User user1 = api.get("/users/123", User.class);  // API call

// Second request within TTL - returns from cache
User user2 = api.get("/users/123", User.class);  // From cache

// After TTL expires - fetches from API again
Thread.sleep(61000);
User user3 = api.get("/users/123", User.class);  // API call

Note: Only GET requests are cached.


Authentication

Bearer Token

ApiManager api = new ApiManager.Builder()
  .baseUrl("https://api.example.com")
  .header("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
  .build();

API Key

ApiManager api = new ApiManager.Builder()
  .baseUrl("https://api.example.com")
  .header("X-API-Key", "your-api-key-here")
  .build();

Basic Auth

import java.util.Base64;

String credentials = "username:password";
String encoded = Base64.getEncoder().encodeToString(
  credentials.getBytes(StandardCharsets.UTF_8)
);

ApiManager api = new ApiManager.Builder()
  .baseUrl("https://api.example.com")
  .header("Authorization", "Basic " + encoded)
  .build();

Dynamic Headers

public void makeAuthenticatedRequest(String token) {
  ApiManager api = new ApiManager.Builder()
    .baseUrl("https://api.example.com")
    .header("Authorization", "Bearer " + token)
    .build();

  try {
    User user = api.get("/me", User.class);
    getLogger().info("Authenticated as: " + user.getName());
  } catch (Exception e) {
    e.printStackTrace();
  }
}

Advanced Usage

Multiple APIs

public class MultiApiPlugin extends DreamPlugin {

  @Override
  public void onDreamEnable() {
    // GitHub API
    ApiPool.register("github", new ApiManager.Builder()
      .baseUrl("https://api.github.com")
      .header("Accept", "application/vnd.github.v3+json")
      .build()
    );

    // Discord API
    ApiPool.register("discord", new ApiManager.Builder()
      .baseUrl("https://discord.com/api/v10")
      .header("Authorization", "Bot YOUR_BOT_TOKEN")
      .build()
    );

    // Custom API
    ApiPool.register("custom", new ApiManager.Builder()
      .baseUrl("https://myapi.example.com")
      .header("X-API-Key", "secret")
      .enableCache(true)
      .cacheTtl(30000)
      .build()
    );
  }

  public void fetchFromMultipleApis() {
    try {
      // GitHub
      var repo = ApiPool.get("github").get("/repos/owner/repo", GitHubRepo.class);

      // Discord
      var guild = ApiPool.get("discord").get("/guilds/123456789", DiscordGuild.class);

      // Custom
      var data = ApiPool.get("custom").get("/data", CustomData.class);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Custom Jackson Mapper

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.DeserializationFeature;

ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

ApiManager api = new ApiManager.Builder()
  .baseUrl("https://api.example.com")
  .mapper(mapper)
  .build();

Query Parameters

// Build URL with query params
String path = "/users?page=1&limit=10";
List<User> users = api.getAll(path, new TypeReference<List<User>>() {});

// Or use helper
String path = String.format("/users?page=%d&limit=%d", 1, 10);

Complex Request

public void createUserWithDetails(String name, String email) {
  String json = String.format(
    "{\"name\":\"%s\",\"email\":\"%s\",\"role\":\"user\"}",
    name, email
  );

  try {
    User user = ApiPool.get("myapi").post("/users", json, User.class);
    getLogger().info("Created user: " + user.getId());
  } catch (ApiManager.ApiRequestException e) {
    getLogger().severe("Failed to create user: " + e.getMessage());
  } catch (Exception e) {
    e.printStackTrace();
  }
}

Best Practices

✅ Do's

  • Register on enable — Initialize APIs in onDreamEnable()
  • Close on disable — Call ApiPool.closeAll() in onDreamDisable()
  • Use async — Make HTTP requests asynchronously
  • Handle exceptions — Always catch exceptions
  • Test with ping() — Check API health on startup
  • Use caching — Enable for frequently accessed data
  • Store tokens safely — Use configuration files
  • Define response models — Create POJOs for type safety

❌ Don'ts

  • Don't block main thread — Use async for all HTTP requests
  • Don't hardcode tokens — Use config files
  • Don't ignore timeouts — Set appropriate timeout values
  • Don't leak connections — Always close ApiManager
  • Don't log sensitive data — Be careful with tokens in logs
  • Don't retry forever — Implement retry limits

Complete Examples

GitHub API Client

public class GitHubPlugin extends DreamPlugin {

  private static final String API_LABEL = "github";

  @Override
  public void onDreamEnable() {
    // Register GitHub API
    ApiPool.register(API_LABEL, new ApiManager.Builder()
      .baseUrl("https://api.github.com")
      .header("Accept", "application/vnd.github.v3+json")
      .timeout(10000)
      .build()
    );

    // Test connection
    if (ApiPool.get(API_LABEL).ping()) {
      getLogger().info("GitHub API connected");
    }
  }

  @Override
  public void onDreamDisable() {
    ApiPool.closeAll();
  }

  public void getRepository(Player player, String owner, String repo) {
    Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
      try {
        String path = String.format("/repos/%s/%s", owner, repo);
        GitHubRepo repository = ApiPool.get(API_LABEL).get(path, GitHubRepo.class);

        Bukkit.getScheduler().runTask(this, () -> {
          player.sendMessage(
            Component.text("Repository: " + repository.getName(), NamedTextColor.GREEN)
          );
          player.sendMessage(
            Component.text("Stars: " + repository.getStargazersCount(), NamedTextColor.GOLD)
          );
          player.sendMessage(
            Component.text("Language: " + repository.getLanguage(), NamedTextColor.AQUA)
          );
        });

      } catch (Exception e) {
        Bukkit.getScheduler().runTask(this, () -> {
          player.sendMessage(
            Component.text("Failed to fetch repository", NamedTextColor.RED)
          );
        });
      }
    });
  }
}

// Response model
class GitHubRepo {
  private String name;
  private String description;
  private int stargazersCount;
  private String language;

  // Getters
  public String getName() { return name; }
  public String getDescription() { return description; }
  public int getStargazersCount() { return stargazersCount; }
  public String getLanguage() { return language; }
}

REST API CRUD

public class UserApiPlugin extends DreamPlugin {

  private ApiManager api;

  @Override
  public void onDreamEnable() {
    api = ApiPool.register("users", new ApiManager.Builder()
      .baseUrl("https://api.example.com")
      .header("Authorization", "Bearer YOUR_TOKEN")
      .enableCache(true)
      .cacheTtl(30000)
      .build()
    );
  }

  // CREATE
  public void createUser(String name, String email) {
    String json = String.format(
      "{\"name\":\"%s\",\"email\":\"%s\"}",
      name, email
    );

    try {
      User user = api.post("/users", json, User.class);
      getLogger().info("Created user: " + user.getId());
    } catch (Exception e) {
      getLogger().severe("Failed to create user: " + e.getMessage());
    }
  }

  // READ
  public User getUser(int id) {
    try {
      return api.get("/users/" + id, User.class);
    } catch (Exception e) {
      getLogger().severe("Failed to get user: " + e.getMessage());
      return null;
    }
  }

  // READ ALL
  public List<User> getAllUsers() {
    try {
      return api.getAll("/users", new TypeReference<List<User>>() {});
    } catch (Exception e) {
      getLogger().severe("Failed to get users: " + e.getMessage());
      return Collections.emptyList();
    }
  }

  // UPDATE
  public void updateUser(int id, String name) {
    String json = String.format("{\"name\":\"%s\"}", name);

    try {
      User user = api.put("/users/" + id, json, User.class);
      getLogger().info("Updated user: " + user.getName());
    } catch (Exception e) {
      getLogger().severe("Failed to update user: " + e.getMessage());
    }
  }

  // DELETE
  public void deleteUser(int id) {
    try {
      api.delete("/users/" + id, DeleteResponse.class);
      getLogger().info("Deleted user: " + id);
    } catch (Exception e) {
      getLogger().severe("Failed to delete user: " + e.getMessage());
    }
  }
}

// Models
class User {
  private int id;
  private String name;
  private String email;

  public int getId() { return id; }
  public String getName() { return name; }
  public String getEmail() { return email; }
}

class DeleteResponse {
  private boolean success;
  public boolean isSuccess() { return success; }
}

Weather API with Caching

public class WeatherPlugin extends DreamPlugin {

  @Override
  public void onDreamEnable() {
    ApiPool.register("weather", new ApiManager.Builder()
      .baseUrl("https://api.weatherapi.com/v1")
      .header("key", "YOUR_API_KEY")
      .enableCache(true)
      .cacheTtl(300000)  // Cache for 5 minutes
      .build()
    );
  }

  public void getWeather(Player player, String city) {
    Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
      try {
        String path = String.format("/current.json?q=%s", city);
        WeatherData weather = ApiPool.get("weather").get(path, WeatherData.class);

        Bukkit.getScheduler().runTask(this, () -> {
          player.sendMessage(
            Component.text("Weather in " + city, NamedTextColor.GOLD)
          );
          player.sendMessage(
            Component.text("Temperature: " + weather.getCurrent().getTempC() + "°C", 
              NamedTextColor.AQUA)
          );
          player.sendMessage(
            Component.text("Condition: " + weather.getCurrent().getCondition().getText(), 
              NamedTextColor.GREEN)
          );
        });

      } catch (Exception e) {
        Bukkit.getScheduler().runTask(this, () -> {
          player.sendMessage(
            Component.text("Failed to fetch weather", NamedTextColor.RED)
          );
        });
      }
    });
  }
}

class WeatherData {
  private Current current;
  public Current getCurrent() { return current; }

  static class Current {
    private double tempC;
    private Condition condition;

    public double getTempC() { return tempC; }
    public Condition getCondition() { return condition; }
  }

  static class Condition {
    private String text;
    public String getText() { return text; }
  }
}

Next Steps

Clone this wiki locally