CodeGym /Courses /Java Server /External HTTP API in ReadLater Starter

External HTTP API in ReadLater Starter

Java Server
Level 14, Lesson 0
Available

1. Backend as an HTTP client

If until now you have imagined the backend in your head as a “server that only accepts requests,” then today we will move that picture a little. In reality, a backend service almost always lives in an environment of other services and APIs: it asks someone for something, sends data to someone else, clarifies details from another service. And that is normal.

Imagine a typical workday for any “ordinary” service: it received a request from a user, then called an external payment provider, then clarified delivery data in a logistics service, then sent an email through a mail API — and only then returned a response to the user. Sometimes it feels like the backend is not really “processing business logic” at all, but carefully conducting other people’s APIs. (Well, a conductor is also a profession, by the way.)

To immediately establish clear terms, it is useful to distinguish two directions:

Direction What happens Who is the “client,” who is the “server”
Inbound HTTP (incoming) Someone knocks on our door client = user/browser/mobile app, server = our backend
Outbound HTTP (outgoing) We knock on someone else’s door client = our backend, server = external API/neighboring service

Today we are dealing specifically with the outbound part: our Java application will send an HTTP request to an external API and receive a response. In other words, it will act... like a very serious Postman, only with Java’s personality and a habit of strict types (for now without typing — but Java is already getting ready morally).

2. Book catalog for ReadLater Starter

The domain of ReadLater Starter is simple and human: a personal “read later” list. But as soon as you try to make this list even a little convenient, the question arises: where do we get the book data from? And here the external catalog is not an “extra,” but a perfectly logical source of information.

Of course, you could force the user to enter everything manually: title, author, year, publisher, and, preferably, also “who translated it.” But then our service would quickly turn into an app called “Guess How the Author’s Last Name Is Spelled Correctly,” and the user would start copying data from the internet by hand. That is both inconvenient and error-prone.

The external book catalog in our project plays the role of a reference data source. In practice, it looks like this: the user enters a search query (for example, clean code), we send a request to the catalog, receive a list of results, the user selects a book by its external identifier, and we can fetch a more detailed card. Even if later we get a local reading list, the catalog remains useful as a “reference book” that can find books faster and more accurately than a person tired after work.

And a critically important methodological point: we have already investigated the contract of this catalog through Postman. That means today we are not “trying to guess the correct URL.” We are doing an engineering-honest thing: moving an already verified contract into code. It is exactly like “measure first with a tape measure, then cut,” not the other way around (although, yes, in programming people sometimes still cut first — but we will try to do better).

3. Local vs external HTTP call

Inside a Java program, we are used to a very cozy model: call a method — get a result. Everything is in one process, one heap, one debugger, you can set a breakpoint and feel like the ruler of the world. With an HTTP call, the brain tries to apply the same model — and that is where the usual “why doesn’t it work?” starts.

For comparison, here is an “ordinary” local call — within one process:

// Local call: everything happens in our process, without network or HTTP statuses
String result = localCatalog.findById("OL12345M");

// Here we print the already prepared result (for example, a string with the book title)
System.out.println(result); // (for example) Clean Code by Robert C. Martin

In this code there is no address, no network, no status, no headers. If the method breaks, you will most likely quickly understand where exactly: debugger, stack trace, everything is nearby.

And now — a hint of an external call. We will not even execute it; just look at the shape:

import java.net.URI;
import java.net.http.HttpRequest;

// Address of the external API: this is no longer a "method call," but a concrete URL/URI
URI uri = URI.create("https://catalog.example/books/OL12345M");

HttpRequest request = HttpRequest.newBuilder(uri)
        // Header: explicitly say that we want to receive JSON
        .header("Accept", "application/json")
        // HTTP method: read data (not modify it)
        .GET()
        .build();

You can immediately see a different universe. An URI appeared, an HTTP method appeared, a header Accept appeared. And most importantly: even if the request looks correct, that does not yet mean success. The server may return 404, may return 500, may return an unexpected response format, or may not answer at all. And that is not “exotic.” That is normal network life.

If you want to capture the differences in one place, here is a very practical table:

Characteristic Local method call HTTP call to an external API
Where the code runs In your process On someone else’s server
Input data Method parameters URI + method + headers (+ sometimes body)
Result Return value Status + headers + body
Error An exception in your code Could be an exception (network), or a valid HTTP response with an error status
“Can you debug inside it?” Usually yes No (it is not your code)

And this is where a key backend developer habit is born: an HTTP call should always be thought of as contract-based interaction, not as “a method that runs somewhere over there.”

4. The HTTP contract of an external call

When you start working with an external API, it is very tempting to think: “well, it’s JSON, I’ll just read it somehow.” But it is more correct to think differently: “I am entering into an agreement with someone else’s service.” In that agreement there are clear points: where and how I knock, and what I am promised in return. Otherwise you will quickly end up in a situation where “it worked yesterday, but today it doesn’t.”

An HTTP contract has several mandatory parts. We have already discussed them before, but today this is important specifically from the perspective of an outgoing call (that is, when the client is us):

Part of the contract Where it appears in the code The question you should ask yourself
URI URI.create(...) Where am I going? Is this the exact address?
Method .GET(), .POST(), … What am I doing: reading or changing?
Headers .header("Accept", "...") What format do I want the response in?
Status code response.statusCode() Is this success or an error? What kind of problem is it?
Body response.body() What exactly came back? Does it look like what we expected?

And here is an important thought: the contract is not only the body. A beginner often looks only at the JSON and forgets that 200 and 404 are fundamentally different stories. Even if in both cases the response body looks like JSON (and sometimes it does), the meaning is completely different.

If we make it very concrete: an external API is like a “receipt in a store.” It is important not only what is inside the bag (the body), but also what is written at the top: whether the purchase was successful (status), what conditions apply (headers), and whether this is even the correct store address (URI). Otherwise you risk bringing home not groceries, but a feeling of embarrassment and a pile of errors.

5. Postman: a code draft

Good news: today we do not need to play “guess the correct endpoint.” We have already gone to the catalog through Postman, already seen request and response examples, already checked variants. In other words, we are doing what real development calls “first contract investigation, then implementation.”

It is convenient to mentally imagine that a Postman request is a draft of future code. Roughly like this (a conditional format, but the meaning is recognizable):

# Search request to the catalog (draft from Postman)
GET {{baseUrl}}/search?q=clean%20code
# Tell the server that we expect JSON in the response
Accept: application/json

These are not just “pretty lines.” This is a fixed agreement: the method GET, a specific path, the query parameter q, and the expectation of a JSON response.

And here is why this step is methodologically important. When you write your first HTTP client in Java, it is very easy to fall into magic: “well, it somehow sends requests.” Postman brings you back to earth: everything is visible there. The method is visible. The URL is visible. The headers are visible. The response is visible. And today’s task is to bring that transparency into code, not hide it under a rug called “a universal client for the future.”

6. Minimal success: status and raw body

On the first step, it is very useful to lower expectations to the right minimum. Today we are not building the ideal architecture, not “designing a client SDK,” and not turning JSON into beautiful Java objects. Our success criterion is simpler and more honest: the application sent a request, received a response, and was able to show us its meaning.

This is where the term we will use throughout the level appears: raw body. It is the response body read “as is,” without mapping into a DTO. In essence, it is a JSON string. And yes, to a Java developer that sounds like “ugh, a string,” but in a learning step this is exactly what we need: first make sure the transport works and the contract matches, and only then think about types.

Schematically, our minimal flow for today looks like this:

sequenceDiagram
    participant U as "User"
    participant A as "ReadLater Starter (Java)"
    participant C as "External book catalog"

    Note over U,A: "The user runs the command and expects an observable result"
    U->>A: "Run the command 'catalog search ...'"
    Note over A,C: "It is important to see the contract: URI, method, headers, status, and body"
    A->>C: "HTTP GET (URI + Accept)"
    C-->>A: "HTTP response (status + headers + JSON body)"
    A-->>U: "Print status and raw JSON (preview)"

Notice that the “response” here is not only JSON. Even at the level of “just print it to the console,” we want to see at least three things: status, headers (at least once to inspect), and body. Because this is exactly how a backend developer learns not to lose important parts of the contract.

7. A repeatable step

It is very important that the first external call does not remain “code in a separate method that nobody ever calls.” By the end of the day, you should be able to start the application and get an observable result: the external request really went out, the response really came back, and you really see JSON. That turns the topic from “theory about HTTP” into a habit.

And at the same time, we are deliberately not overcomplicating things. For now, even such a “skeleton of interaction” will be enough (for now without implementation details, just as an idea):

// Execute a request to the catalog and fetch the "raw" JSON as a string
String json = catalogClient.searchRaw("clean code");

// For the first step, it is convenient to print a preview so we do not flood the console with megabytes of JSON
int previewLength = Math.min(json.length(), 200);
System.out.println(json.substring(0, previewLength) + "...");
// { "docs": [ ... ] }...

This little piece is important not because it is “beautiful,” but because it makes external integration part of the application. That means we can repeat the run, compare responses, understand what changes, and gradually bring the code into order. This is exactly the same principle by which we previously fixed the Gradle Wrapper and startup commands: if a step can be repeated, then it really exists, instead of having “accidentally worked once on my computer.”

And yes, at this moment you may feel the urge to immediately wrap everything in ten layers of abstractions. That is a normal urge, especially if you have seen beautiful architecture diagrams. But today our task is the opposite: keep the mechanics visible so the brain gets used to the reality of an HTTP call. We will add beauty and “adult layers” later — when there is something to layer on top of.

8. Typical mistakes in the first outbound HTTP call

When you are just starting to make outgoing HTTP calls, you inevitably compare them with familiar method calls and expect the same behavior. That is where the mistakes come from that are common in practice even among smart people — simply because “the brain has not yet accepted the rules of the game.” It is better to catch them at the shore.

Mistake #1: thinking that the backend “only accepts requests,” and that “going outward” is something rare.
Because of this mindset, the external call is perceived as a strange one-off feature, not a normal part of the system. As a result, the code gets thrown together wherever it fits, without discipline. It is useful to accept right away: outbound HTTP is just as ordinary a routine as working with strings (unfortunately).

Mistake #2: mentally equating an HTTP call with someService.method() and ignoring the contract.
As soon as you forget that HTTP has an address, method, headers, and status codes, you start looking only at the body. That is dangerous: you can receive an error status and still try to read the body as if it were a successful response. The right habit: status first, everything else after.

Mistake #3: hiding the first call behind a “universal client for the future.”
This is the case where “architecture” kills learning. If on the first step you create five classes and three interfaces, you will lose the most valuable thing — understanding how a request and response really look. At the starting stage, one small clear call is better than a “perpetual motion machine” of abstractions.

Mistake #4: writing code without relying on the already investigated Postman contract.
Sometimes you want to say: “well, I’ll quickly throw together the URL as a string.” In the end you spend time guessing instead of learning. If the contract has already been investigated, use it as a map. A Postman collection is not an archive “for memory”; it is working documentation for the code.

Mistake #5: treating raw JSON in the console as the “final result of the task.”
Raw JSON is an excellent intermediate result, but only intermediate. If you stop here, the application will remain at the level of “printing incomprehensible text.” Today, raw is a tool for checking the contract and the transport. Later (not today) we will make the response suitable for real use in code.

1
Task
Java Server, level 14, lesson 0
Locked
Local Call and External Contract
Local Call and External Contract
1
Task
Java Server, level 14, lesson 0
Locked
The `catalog search` command as an outgoing request description
The `catalog search` command as an outgoing request description
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION