📣 Requestly API Client – Free Forever & Open Source. A powerful alternative to Postman. Try now ->

How to Set Up a Java-Based Mock Server

Rohit Rajpal
Learn how to set up a Java-based mock server with step-by-step methods, configuration tips, and advanced scenarios for effective API testing.
How to Set Up a Java-Based Mock Server

Setting up a Java-based mock server allows development and testing teams to simulate backend APIs without relying on live systems. It creates a controlled environment where responses can be predefined, altered, and monitored, making it easier to validate application behavior under specific scenarios.

To make such a setup effective, teams need to configure endpoints, define request-response mappings, manage dependencies, and integrate with build and test frameworks.

This article covers what a Java-based mock server is, how to approach a Java-based mock server setup, and the key factors that make it effective for testing.

What is a Java-Based Mock Server?

A Java-based mock server is a local or remote server built with Java that mimics the behavior of a real API. It listens for HTTP or HTTPS requests and returns predefined responses that match specific request patterns. By controlling the responses, teams can test how applications handle different scenarios without relying on live systems.

Unlike generic mock data files, a mock server operates like a functional service. It can simulate various endpoints, apply conditional logic to responses, and support both JSON and XML payloads. This makes it useful for validating application behavior during development, integration testing, and performance testing.

Why is a Java-Based Mock Server Needed?

In most projects, API dependencies are not always available or ready for integration testing. Third-party APIs might have rate limits, require authentication tokens, or incur costs for each request. Even internal APIs can be unstable during development or undergo frequent changes that affect dependent teams.

A Java-based mock server setup addresses these challenges by:

  • Removing external dependencies: Applications can be tested without waiting for actual API readiness or uptime.
  • Enabling controlled scenarios: Specific responses, delays, and error codes can be configured to test edge cases.
  • Supporting parallel development: Frontend and backend teams can work independently by simulating API behavior.
  • Reducing testing costs: Avoids the expense of paid API calls during repeated test runs.

How Java-Based Mock Server Setup Works

A Java-based mock server processes requests in a defined sequence to ensure consistent and predictable responses. The general flow is:

  1. Intercept the request: The server listens for incoming HTTP or HTTPS requests on the configured port.
  2. Match against rules: It compares the request to predefined rules based on the endpoint path, HTTP method, query parameters, or headers.
  3. Select the matching response: If a match is found, the server uses the associated response configuration, which includes the body, status code, and headers.
  4. Handle unmatched requests: If no rule matches, the server can return a default response, send an error, or forward the request to a live API if proxying is enabled.
  5. Support runtime updates: In some setups, rules and responses can be updated while the server is running, avoiding restarts.

Methods to Set Up a Java-Based Mock Server

There are different ways to create a mock server in Java, depending on the level of customization required. Below are practical methods with example code snippets to help you get started.

1. Creating a Basic Mock Server with Java’s HttpServer

Java’s built-in com.sun.net.httpserver.HttpServer class is part of the JDK and provides a lightweight way to create simple HTTP servers without requiring external libraries. It is ideal for quickly setting up mock endpoints for testing purposes, especially when you want to avoid adding new dependencies to your project.

The server can be configured to listen on a specific port, match request paths, and send predefined responses, making it a straightforward option for simulating APIs during development.

Here’s an example that sets up a basic mock server to respond with a static JSON payload when a specific endpoint is called:

				
					import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

public class BasicMockServer {
    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
        server.createContext("/api/data", new MyHandler());
        server.setExecutor(null); // creates a default executor
        System.out.println("Mock server is running on http://localhost:8080/api/data");
        server.start();
    }

    static class MyHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange exchange) throws IOException {
            String jsonResponse = "{\"message\":\"Hello from mock server\"}";
            exchange.getResponseHeaders().set("Content-Type", "application/json");
            exchange.sendResponseHeaders(200, jsonResponse.getBytes().length);
            try (OutputStream os = exchange.getResponseBody()) {
                os.write(jsonResponse.getBytes());
            }
        }
    }
}
				
			

This example creates a simple HTTP server on port 8080 that returns a JSON message for requests to /api/data. It can be extended to handle multiple endpoints or dynamic responses as needed.

2. Using Requestly for Configurable Java-Based Mock Endpoints

Requestly is a browser-based platform that allows teams to intercept and mock API requests without changing application code.

While it is not a traditional Java server library, it can be used alongside Java applications during development by directing API calls to Requestly-configured mock endpoints. This reduces setup complexity and allows quick updates to responses without redeployment.

Here’s a simple example of a Java client making a GET request to a mock API endpoint configured in Requestly:

				
					import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class RequestlyMockClient {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://mock.requestly.io/api/user");
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("GET");

        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuilder content = new StringBuilder();

        while ((inputLine = in.readLine()) != null) {
            content.append(inputLine);
        }
        in.close();
        con.disconnect();

        System.out.println("Response from mock API: " + content.toString());
    }
}
				
			

This example demonstrates how a Java application can consume a mock API served via Requestly, making it easy to change responses during development without code changes.

It is best suited for teams that want quick response changes without rebuilding or deploying their Java backend.

3. Implementing Rule-Based Mock Responses in Java

For more flexible mock servers, it’s common to separate request-response mappings into configuration files or databases. This allows testers or developers to update mock behaviors without changing code. The Java mock server reads these rules at runtime and applies them when matching requests.

Below is a simplified example illustrating how you might load rules from a JSON file and use them to respond dynamically:

				
					[
    {
        "path": "/api/products",
        "method": "GET",
        "status": 200,
        "body": "[{\"id\":1,\"name\":\"Laptop\"}]"
    }
]
				
			

Java code snippet to load and apply rules:

				
					import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

import java.io.*;
import java.net.InetSocketAddress;
import java.util.List;

public class ConfigurableMockServer {

    static class MockRule {
        public String path;
        public String method;
        public int status;
        public String body;
    }

    private static List<MockRule> loadRules(String filename) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(new File(filename), new TypeReference<List<MockRule>>() {});
    }

    public static void main(String[] args) throws Exception {
        List<MockRule> rules = loadRules("mock-rules.json");

        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);

        for (MockRule rule : rules) {
            server.createContext(rule.path, new HttpHandler() {
                @Override
                public void handle(HttpExchange exchange) throws IOException {
                    if (exchange.getRequestMethod().equalsIgnoreCase(rule.method)) {
                        exchange.getResponseHeaders().set("Content-Type", "application/json");
                        exchange.sendResponseHeaders(rule.status, rule.body.getBytes().length);
                        try (OutputStream os = exchange.getResponseBody()) {
                            os.write(rule.body.getBytes());
                        }
                    } else {
                        exchange.sendResponseHeaders(405, -1); // Method Not Allowed
                    }
                }
            });
        }

        server.setExecutor(null);
        server.start();
        System.out.println("Configurable mock server started on port 8080");
    }
}
				
			

This approach is best suited for teams that need a flexible mock server supporting multiple endpoints with responses that can be updated without recompiling code.

4. JUnit Integration for Test-Controlled Mock Servers

Embedding a mock server directly within JUnit test cases enables tests to run with isolated mock environments. The server can start before tests and shut down afterward, ensuring a clean state and repeatability.

Here’s an example using JUnit 5 with setup and teardown methods:

				
					import com.sun.net.httpserver.HttpServer;
import org.junit.jupiter.api.*;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

public class MockServerJUnitTest {

    static HttpServer server;

    @BeforeAll
    public static void startServer() throws IOException {
        server = HttpServer.create(new InetSocketAddress(8080), 0);
        server.createContext("/api/test", exchange -> {
            String response = "{\"status\":\"success\"}";
            exchange.getResponseHeaders().set("Content-Type", "application/json");
            exchange.sendResponseHeaders(200, response.length());
            try (OutputStream os = exchange.getResponseBody()) {
                os.write(response.getBytes());
            }
        });
        server.setExecutor(null);
        server.start();
        System.out.println("Mock server started for tests");
    }

    @AfterAll
    public static void stopServer() {
        server.stop(0);
        System.out.println("Mock server stopped after tests");
    }

    @Test
    public void testMockEndpoint() {
        // Your test code here that calls http://localhost:8080/api/test
        Assertions.assertTrue(true); // Placeholder
    }
}
				
			

JUnit integration is best suited for teams running automated unit or integration tests that require controlled API responses within the test lifecycle.

5. Command Line Setup for Standalone Mock Server

Running a Java-based mock server as a standalone process can simplify sharing the mock API with the team. This can be done by packaging the mock server as a runnable JAR and launching it from the command line without embedding it in other applications.

Example:

Package your mock server class (like the ones above) into a JAR with a manifest specifying the main class. Then run:

				
					java -jar mock-server.jar
				
			

This makes the mock server accessible on a fixed port for development, manual testing, or demos.

Configuring a Java-Based Mock Server

After setting up a mock server, the next step is to configure it to respond correctly to client requests. This involves defining expectations like how the server matches requests and specifying the responses it should send.

Here’s how.

1. Setting Up Expectations and Responses

Expectations tell the mock server what requests to look for and how to respond. These typically include the HTTP method, request path, query parameters, headers, and sometimes the request body.

A basic expectation maps a request pattern to a predefined response:

				
					server.createContext("/api/users", exchange -> {
    if ("GET".equalsIgnoreCase(exchange.getRequestMethod())) {
        String responseBody = "[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"}]";
        exchange.getResponseHeaders().set("Content-Type", "application/json");
        exchange.sendResponseHeaders(200, responseBody.getBytes().length);
        try (OutputStream os = exchange.getResponseBody()) {
            os.write(responseBody.getBytes());
        }
    } else {
        exchange.sendResponseHeaders(405, -1); // Method Not Allowed
    }
});
				
			

In this example, the server responds to GET /api/users with a JSON list of users. You can set different expectations for other HTTP methods or paths similarly.

2. Request Matching & Customization

More advanced mock servers support detailed request matching. This includes checking query parameters, headers, or even parts of the request body to decide which response to send.

Here’s an example that matches a query parameter:

				
					server.createContext("/api/products", exchange -> {
    String query = exchange.getRequestURI().getQuery(); // e.g., "category=electronics"
    if ("GET".equalsIgnoreCase(exchange.getRequestMethod()) && query != null && query.contains("category=electronics")) {
        String responseBody = "[{\"id\":101,\"name\":\"Smartphone\"}]";
        exchange.getResponseHeaders().set("Content-Type", "application/json");
        exchange.sendResponseHeaders(200, responseBody.getBytes().length);
        try (OutputStream os = exchange.getResponseBody()) {
            os.write(responseBody.getBytes());
        }
    } else {
        exchange.sendResponseHeaders(404, -1); // Not Found
    }
});
				
			

Customization can also involve returning different status codes or varying delays to simulate latency or errors.

3. Logging and Debugging Configuration

Effective logging helps track incoming requests and responses for easier debugging. You can add simple logging inside request handlers:

				
					server.createContext("/api/orders", exchange -> {
    System.out.println("Received request: " + exchange.getRequestMethod() + " " + exchange.getRequestURI());
    
    String responseBody = "{\"orderId\":5001,\"status\":\"processed\"}";
    exchange.getResponseHeaders().set("Content-Type", "application/json");
    exchange.sendResponseHeaders(200, responseBody.getBytes().length);
    try (OutputStream os = exchange.getResponseBody()) {
        os.write(responseBody.getBytes());
    }
});
				
			

For larger projects, consider integrating a logging framework like SLF4J or Log4J to manage log levels and output destinations more effectively.

Advanced Setup Scenarios

Mock servers often need to handle more complex scenarios beyond basic request-response mapping. Two common advanced requirements are enabling HTTPS/SSL support and proxying certain requests to real backend services when needed.

1. Configuring HTTPS/SSL

Running your mock server over HTTPS is essential when the client requires secure communication or strict security policies are enforced. Configuring HTTPS involves setting up SSL certificates and configuring the server to use them.

Below is a simplified example demonstrating how to enable HTTPS using a self-signed certificate in Java’s HttpServer:

				
					import com.sun.net.httpserver.HttpsServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.*;
import java.net.InetSocketAddress;
import java.security.KeyStore;

public class HttpsMockServer {

    public static void main(String[] args) throws Exception {
        // Load keystore containing SSL certificate
        char[] keystorePassword = "password".toCharArray();
        KeyStore ks = KeyStore.getInstance("JKS");
        try (FileInputStream fis = new FileInputStream("keystore.jks")) {
            ks.load(fis, keystorePassword);
        }

        // Setup key and trust managers
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(ks, keystorePassword);

        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
        tmf.init(ks);

        // Setup SSL context
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

        // Create HTTPS server
        HttpsServer server = HttpsServer.create(new InetSocketAddress(8443), 0);
        server.setHttpsConfigurator(new com.sun.net.httpserver.HttpsConfigurator(sslContext));

        server.createContext("/secure-api", exchange -> {
            String response = "{\"message\": \"Secure response\"}";
            exchange.getResponseHeaders().set("Content-Type", "application/json");
            exchange.sendResponseHeaders(200, response.getBytes().length);
            try (OutputStream os = exchange.getResponseBody()) {
                os.write(response.getBytes());
            }
        });

        server.setExecutor(null);
        server.start();
        System.out.println("HTTPS mock server running on port 8443");
    }
}
				
			

Key points:

  • A Java KeyStore (keystore.jks) that contains your SSL certificate is required.
  • The server listens on port 8443 for secure HTTPS requests.
  • Clients need to trust the certificate for successful connections (self-signed certs may require manual trust).

2. Proxying Requests to Real Services

Sometimes, the mock server should forward certain requests to the actual backend service, either because the real API is available or to combine real and mock data for testing. Proxying allows the selective routing of requests to real endpoints.

Here is an example illustrating how to forward unmatched requests to a real backend URL:

				
					import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;

public class ProxyMockServer {

    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);

        server.createContext("/api", new HttpHandler() {
            @Override
            public void handle(HttpExchange exchange) throws IOException {
                String path = exchange.getRequestURI().getPath();

                if ("/api/mock".equals(path)) {
                    // Return mock response
                    String response = "{\"mock\":\"data\"}";
                    exchange.getResponseHeaders().set("Content-Type", "application/json");
                    exchange.sendResponseHeaders(200, response.getBytes().length);
                    try (OutputStream os = exchange.getResponseBody()) {
                        os.write(response.getBytes());
                    }
                } else {
                    // Proxy other requests
                    URL url = new URL("https://real.backend.service" + path);
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.setRequestMethod(exchange.getRequestMethod());

                    // Copy headers from original request
                    exchange.getRequestHeaders().forEach((k, v) -> v.forEach(value -> conn.addRequestProperty(k, value)));

                    conn.setDoInput(true);
                    conn.setDoOutput(false);

                    int responseCode = conn.getResponseCode();
                    InputStream is = responseCode >= 400 ? conn.getErrorStream() : conn.getInputStream();

                    exchange.getResponseHeaders().putAll(conn.getHeaderFields());
                    exchange.sendResponseHeaders(responseCode, conn.getContentLengthLong());

                    try (OutputStream os = exchange.getResponseBody()) {
                        if (is != null) {
                            is.transferTo(os);
                        }
                    }
                }
            }
        });

        server.setExecutor(null);
        server.start();
        System.out.println("Proxy mock server running on port 8080");
    }
}
				
			

Notes:

  • Requests to /api/mock return a mock response.
  • Other requests are forwarded to the real backend URL by opening an HTTP connection.
  • Headers and response codes from the backend are passed back to the client transparently.

3. Extending with Custom Code (Classpaths, Initializers)

A flexible mock server often needs to support complex behaviors by adding custom Java classes or initializers that run when the server starts. This lets teams implement dynamic responses or set up logic outside the core server code.

You can load custom handlers from the classpath to keep the server modular. For example, define an interface for handlers and implement it in separate classes:

				
					public interface CustomHandler {
    void handle(HttpExchange exchange) throws IOException;
}

public class DynamicUserHandler implements CustomHandler {
    @Override
    public void handle(HttpExchange exchange) throws IOException {
        String response = "{\"userId\":123,\"name\":\"Dynamic User\"}";
        exchange.getResponseHeaders().set("Content-Type", "application/json");
        exchange.sendResponseHeaders(200, response.length());
        try (OutputStream os = exchange.getResponseBody()) {
            os.write(response.getBytes());
        }
    }
}
				
			

At startup, load and register these dynamically:

				
					Class<?> handlerClass = Class.forName("your.package.DynamicUserHandler");
CustomHandler handlerInstance = (CustomHandler) handlerClass.getDeclaredConstructor().newInstance();

server.createContext("/api/dynamic-user", exchange -> handlerInstance.handle(exchange));
				
			

Initializers can prepare data or configuration before the server starts handling requests. For example:

				
					public class DataInitializer {
    public static Map<Integer, String> users = new HashMap<>();

    public static void initialize() {
        users.put(1, "Alice");
        users.put(2, "Bob");
    }
}
				
			

Call this in the main method:

				
					DataInitializer.initialize();
				
			

4. Handling Static Files & Data Initialization

Mock servers frequently serve static files like JSON or preload data to simulate realistic API responses efficiently.

To serve static files, create contexts that read files from the filesystem and return them:

				
					server.createContext("/static", exchange -> {
    String filePath = "mock-data" + exchange.getRequestURI().getPath().replace("/static", "");
    File file = new File(filePath);
    if (file.exists()) {
        exchange.getResponseHeaders().set("Content-Type", "application/json");
        exchange.sendResponseHeaders(200, file.length());
        try (OutputStream os = exchange.getResponseBody();
             FileInputStream fis = new FileInputStream(file)) {
            fis.transferTo(os);
        }
    } else {
        exchange.sendResponseHeaders(404, -1);
    }
});
				
			

To improve performance, preload data into memory during startup:

				
					public class StaticData {
    public static Map<String, String> responses = new HashMap<>();

    public static void load() throws IOException {
        Path dir = Paths.get("mock-data");
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.json")) {
            for (Path path : stream) {
                String content = Files.readString(path);
                responses.put(path.getFileName().toString(), content);
            }
        }
    }
}
				
			

Use this preloaded data in your request handlers:

				
					server.createContext("/static/data.json", exchange -> {
    String response = StaticData.responses.get("data.json");
    if (response != null) {
        exchange.getResponseHeaders().set("Content-Type", "application/json");
        exchange.sendResponseHeaders(200, response.length());
        try (OutputStream os = exchange.getResponseBody()) {
            os.write(response.getBytes());
        }
    } else {
        exchange.sendResponseHeaders(404, -1);
    }
});
				
			

Why Use Requestly for Java-Based Mock Server Setup?

Requestly by BrowserStack offers a flexible, no-code way to create and manage mock API endpoints, which complements Java-based mock servers by reducing development overhead. It enables teams to quickly define, update, and test mock responses without modifying or redeploying Java code.

Key advantages include:

  • Rapid Response Changes: Modify mock API responses on the fly through an easy-to-use interface, avoiding the need to restart or rebuild your Java server.
  • Browser-Level Mocking: Intercept and mock requests directly in browsers during frontend development or testing, simplifying integration with Java backends.
  • Minimal Setup: Unlike traditional mock servers, Requestly requires no additional Java libraries or server setup, reducing configuration complexity.
  • Supports Complex Scenarios: You can configure request matching, delays, and error simulations without code, complementing Java-based logic.
  • Collaboration Friendly: Centralized mock API management helps teams share and synchronize test environments efficiently.

Conclusion

A Java-based mock server helps simulate APIs for development and testing, offering control and flexibility to create realistic test environments. By configuring endpoints, customizing request handling, and extending functionality, teams can create reliable and flexible mock environments that mirror real-world scenarios.

Requestly enhances this process by offering a no-code, easy-to-use platform for managing mock APIs alongside your Java setup. It allows rapid updates to mock responses without code changes or server restarts, supports complex request matching, and integrates smoothly with browser-based testing.

Written by
Rohit Rajpal
Rohit Rajpal is a B2B Content Strategist at BrowserStack, helping SaaS brands build AI-first strategies that drive authority and revenue. He writes about content, strategy, and the future of search in the zero-click era.

Related posts