This project uses Java records to hold data. Records are a simple way to create classes that just hold values.
We have two records:
Quote- the outer wrapper withtypeandvalueValue- the inner object withidandquote
A record is a special kind of class that holds data. Java creates the constructor, getters, equals, hashCode, and toString for you.
public record Value(Long id, String quote) { }This one line gives you:
- A constructor:
new Value(1L, "some quote") - Getter methods:
value.id()andvalue.quote() equals()andhashCode()based on all fields- A nice
toString()for debugging
The old way (without records):
public class Value {
private final Long id;
private final String quote;
public Value(Long id, String quote) {
this.id = id;
this.quote = quote;
}
public Long getId() { return id; }
public String getQuote() { return quote; }
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
@Override
public String toString() { ... }
}Records save you from writing all that boilerplate.
@JsonIgnoreProperties(ignoreUnknown = true)
public record Value(Long id, String quote) { }Matches this JSON:
{
"id": 5,
"quote": "Spring Boot gives your project the essentials."
}@JsonIgnoreProperties(ignoreUnknown = true)
public record Quote(String type, Value value) { }Matches this JSON:
{
"type": "success",
"value": {
"id": 5,
"quote": "Spring Boot gives your project the essentials."
}
}Notice that Quote has a Value inside it. This matches the nested JSON structure.
@JsonIgnoreProperties(ignoreUnknown = true)
public record Quote(String type, Value value) { }What this does:
- If the JSON has extra fields we did not define, ignore them.
- Without this, Jackson would throw an error on unknown fields.
Example:
If the API returns:
{
"type": "success",
"value": { ... },
"timestamp": "2024-01-15T10:30:00Z"
}We did not define a timestamp field in our record. With @JsonIgnoreProperties(ignoreUnknown = true), Jackson just ignores it and the code still works.
Why use this?
- APIs can change and add new fields.
- Your code keeps working even if you don't update the record immediately.
- It makes your code more robust.
When we call:
restClient.get().uri("/api/random").retrieve().body(Quote.class)Here is what happens:
RestClientsends a GET request to the URL.- The response is JSON text.
- Jackson (a JSON library) reads the JSON.
- Jackson sees we want a
Quoteobject. - Jackson matches JSON field names to record field names:
"type"→Quote.type"value"→Quote.value(which is aValuerecord)"id"→Value.id"quote"→Value.quote
- Jackson creates the objects:
new Value(5, "..."), thennew Quote("success", value). - We get back a
Quoteobject we can use in Java.
The key rule: Your record field names must match the JSON key names exactly.
Once you have a Quote object, you can access the data:
Quote quote = restClient.get().uri("/api/random").retrieve().body(Quote.class);
// Access fields using getter methods (note: no "get" prefix)
String type = quote.type(); // "success"
Value value = quote.value(); // the nested Value object
Long id = value.id(); // 5
String text = value.quote(); // "Spring Boot gives..."Records cannot be changed after creation. There are no setter methods.
Value v = new Value(1L, "hello");
// v.setId(2L); // ERROR - no such method
// v.id = 2L; // ERROR - field is finalWhy immutable?
- Safer: No accidental changes.
- Thread-safe: Multiple threads can read without locks.
- Predictable: The object always has the same values.
If you need different values, create a new record:
Value v1 = new Value(1L, "hello");
Value v2 = new Value(2L, "world"); // new object, different values