Lombok, Jackson, AWS SDK β The magic behind the HOTW code
Table of Contents
- What are Annotations?
- Lombok β Stop Writing Boilerplate
- Jackson β Java β JSON Conversion
- AWS SDK β Talking to AWS Services
- Putting it All Together β A Model Class Explained
1. What are Annotations?
Annotations are labels starting with @ that give instructions to the compiler or framework.
@Override // "This method overrides a parent class method"
@Deprecated // "Don't use this method anymore"
@SuppressWarnings // "Don't show compiler warnings for this"
They donβt change what your code DOES β they give metadata that tools (Lombok, Jackson, Spring) read to generate or modify code.
How annotations work in our code:
Your .java file with @Data @Builder
β
Lombok reads annotations during COMPILATION
β
Lombok GENERATES extra code (getters, setters, builder, etc.)
β
Compiled .class file has all the generated code
β
Your code can use getFleetId(), builder(), etc.
2. Lombok
Lombok is a library that automatically generates repetitive Java code. You write ONE annotation, Lombok writes 50 lines.
@Data β generates everything for a data class:
@Data
public class OrderDetails {
private String fleetId;
private String peakTpm;
private String bauTpm;
}
// @Data automatically generates:
// β
getFleetId()
// β
setFleetId(String fleetId)
// β
getPeakTpm()
// β
setPeakTpm(String peakTpm)
// β
getBauTpm()
// β
setBauTpm(String bauTpm)
// β
equals() β compare two OrderDetails objects
// β
hashCode() β hash code based on all fields
// β
toString() β "OrderDetails(fleetId=X, peakTpm=Y, ...)"
// Without @Data, you'd write ~100 lines of repetitive code!
@Builder β generates the builder pattern:
@Builder
public class CreateHotwRunDetailsInput {
private int runId;
private String runStatus;
private String eventId;
private String orderType;
}
// @Builder automatically generates a builder class, so you can write:
CreateHotwRunDetailsInput input = CreateHotwRunDetailsInput.builder()
.runId(42)
.runStatus("InProgress")
.eventId("PrimeDay26")
.orderType("Standard")
.build();
@Jacksonized β makes @Builder work with Jackson:
// Problem: @Builder creates a private constructor,
// but Jackson needs a way to create objects when parsing JSON.
// Solution: @Jacksonized makes them work together.
@Builder
@Jacksonized // β tells Jackson "use the builder to deserialize"
public class PreferredASG {
private int recordId;
private String asgs;
}
// Now Jackson can convert this JSON:
// {"RecordId": 1, "ASGS": "[\"asg-1\", \"asg-2\"]"}
// INTO a PreferredASG object automatically!
@AllArgsConstructor β generates a constructor with ALL fields:
@AllArgsConstructor
public class HardwareOrdersUtil {
private LambdaLogger logger;
private static Map<String, ...> scalingPlannerApiServiceProxyByRegion;
}
// Generates:
public HardwareOrdersUtil(LambdaLogger logger) {
this.logger = logger;
}
// Note: static fields are NOT included in constructors
@AllArgsConstructor(access = AccessLevel.PRIVATE):
@AllArgsConstructor(access = AccessLevel.PRIVATE) // private constructor!
public class EPICBackendHotwApiCallsCommon {
private final EPICBackendApiProxy epicBackendApiProxy;
private final LambdaLogger logger;
// Forces users to use the factory method instead of new():
public static EPICBackendHotwApiCallsCommon getInstance(
EPICBackendApiProxy proxy, LambdaLogger logger) {
return new EPICBackendHotwApiCallsCommon(proxy, logger);
// β only works because it's inside the class
}
}
// Outside the class:
// new EPICBackendHotwApiCallsCommon(...) β ERROR β constructor is private!
EPICBackendHotwApiCallsCommon.getInstance(...) β
use factory method
@SuperBuilder β builder that works with inheritance:
// When a class EXTENDS another class, use @SuperBuilder:
@SuperBuilder
@JsonIgnoreProperties(ignoreUnknown = true)
public static class HotwExecutionDetails {
private Integer peakTPM;
private Integer bauTPM;
// ...
}
// Why? Regular @Builder doesn't know about parent class fields.
// @SuperBuilder handles inheritance properly.
Complete Lombok reference for HOTW code:
| Annotation | What it generates |
|---|---|
@Data |
Getters + Setters + equals + hashCode + toString |
@Builder |
Builder class + static builder() method |
@Jacksonized |
Makes @Builder work with Jackson JSON parsing |
@AllArgsConstructor |
Constructor with all fields as parameters |
@AllArgsConstructor(access = PRIVATE) |
Same but private β forces use of factory |
@SuperBuilder |
Builder that works with class inheritance |
3. Jackson
Jackson is a library for converting between Java objects β JSON.
JSON to Java (Deserialization):
// Input JSON:
{
"RunId": 42,
"RunStatus": "InProgress",
"EventId": "PrimeDay26",
"OrderType": "Standard"
}
// Jackson converts it to Java object:
CreateHotwRunDetailsInput input = objectMapper.readValue(jsonString, CreateHotwRunDetailsInput.class);
// Now you can use:
int id = input.getRunId(); // 42
String status = input.getRunStatus(); // "InProgress"
Java to JSON (Serialization):
CreateHotwRunDetailsInput input = CreateHotwRunDetailsInput.builder()
.runId(42)
.runStatus("InProgress")
.build();
String json = objectMapper.writeValueAsString(input);
// Result: {"RunId":42,"RunStatus":"InProgress","EventId":null}
@JsonProperty β map Java field name to JSON key:
public class CreateHotwRunDetailsInput {
@JsonProperty("RunId") // JSON key is "RunId" (capital R)
int runId; // Java field is "runId" (lowercase r)
@JsonProperty("RunStatus") // JSON key is "RunStatus"
String runStatus; // Java field is "runStatus"
}
Without @JsonProperty:
- Jackson looks for
"runId"in JSON (lowercase r) - But JSON has
"RunId"(capital R) - β Jackson cannot find it β field stays null!
With @JsonProperty("RunId"):
- Tells Jackson: βthe JSON key is RunId, map it to my runId fieldβ
- β Works correctly!
@JsonIgnoreProperties β ignore unknown JSON fields:
@JsonIgnoreProperties(ignoreUnknown = true)
public class AtomicHOTWRequestModel {
@JsonProperty("PreviousVersionId")
private String previousVersionId;
@JsonProperty("CurrentVersionId")
private String currentVersionId;
}
If the incoming JSON has extra fields not in your Java class:
{
"PreviousVersionId": "v1",
"CurrentVersionId": "v2",
"ExtraFieldNotInJava": "something", β extra field
"AnotherUnknownField": 123 β extra field
}
- Without
@JsonIgnoreProperties: Jackson throws an error β βUnknown field!β - With
@JsonIgnoreProperties(ignoreUnknown = true): Jackson silently ignores unknown fields β
This is important because APIs sometimes add new fields β your code should not break.
@JsonInclude β control what gets included in JSON output:
@JsonInclude(JsonInclude.Include.NON_NULL) // Don't include null fields in JSON
public class HOTWHelperModel {
private Integer hostNeeded; // null β NOT included in JSON output
private String hardwareOrderSimLink; // null β NOT included in JSON output
private Fleet fleet; // null β NOT included in JSON output
}
Without @JsonInclude:
{
"hostNeeded": null,
"hardwareOrderSimLink": null,
"fleet": null
}
With @JsonInclude(NON_NULL):
{} // empty object β no null fields included
Or if only some fields have values:
{
"hostNeeded": 50
}
TypeReference β tell Jackson the EXACT type for complex generics:
// Jackson needs to know: "what kind of List is this?"
// TypeReference gives Jackson the type information at runtime:
List<PreferredASG> list = JsonUtil.parseJson(
response,
new TypeReference<>() {} // β anonymous class β tells Jackson "List<PreferredASG>"
);
// Without TypeReference, Jackson can't know the generic type <PreferredASG>
// and would just give you List<LinkedHashMap> which is useless!
// Another example:
List<HotwExecutionAllDetailsInput.HotwExecutionDetails> history =
JsonUtil.parseJson(response, new TypeReference<>() {});
Complete Jackson annotation reference:
| Annotation | Purpose |
|---|---|
@JsonProperty("Name") |
Map this field to JSON key βNameβ |
@JsonIgnoreProperties(ignoreUnknown = true) |
Skip unknown JSON fields |
@JsonInclude(NON_NULL) |
Donβt serialize null fields to JSON |
TypeReference<T> |
Runtime type token for generic deserialization |
4. AWS SDK
The AWS SDK lets Java code talk to Amazon Web Services.
Amazon SNS (Simple Notification Service):
SNS is a pub/sub messaging service β you publish a message to a topic, and all subscribers get it.
// Create SNS client (using default AWS credentials from environment):
AmazonSNS sns = AmazonSNSClientBuilder.defaultClient();
// Build the message:
PublishRequest request = new PublishRequest()
.withTopicArn("arn:aws:sns:us-east-1:123456789:MyTopic")
.withMessage("{\"FleetId\": \"FleetA-NA\", \"EventId\": \"PrimeDay26\"}")
.withMessageAttributes(messageAttributes); // optional metadata
// Publish (send it):
sns.publish(request);
Message Attributes β metadata attached to SNS messages:
MessageAttributeValue attribute = new MessageAttributeValue()
.withDataType("String") // type of value
.withStringValue("HARDWARE_ORDER"); // the actual value
Map<String, MessageAttributeValue> attrs = new HashMap<>();
attrs.put("NotificationName", attribute); // key = attribute name
Subscribers can filter messages based on attributes β so a notification Lambda only gets βHARDWARE_ORDERβ messages, not all messages.
Amazon SQS (Simple Queue Service):
SQS is a queue β you send a message, something else reads it later.
// Create SQS client:
AmazonSQS sqs = AmazonSQSClientBuilder.defaultClient();
// Get queue URL by name:
String queueUrl = sqs.getQueueUrl("my-queue-name").getQueueUrl();
// Send a message:
SendMessageRequest messageRequest = new SendMessageRequest()
.withQueueUrl(queueUrl)
.withMessageBody("FleetA-NA,PrimeDay26")
.withDelaySeconds(600); // delay 600 seconds before it's visible
sqs.sendMessage(messageRequest);
AWS Lambda Context:
Every Lambda function receives a Context object when triggered. It provides:
public class HotwUpscalingHelper {
public HotwUpscalingHelper(Context context) {
// Get a logger β log to CloudWatch
LambdaLogger logger = context.getLogger();
// Log a message:
logger.log("Starting HOTW workflow...");
// This appears in AWS CloudWatch Logs
}
}
5. Putting it All Together β A Model Class Explained
Letβs take HotwExecutionAllDetailsInput.java and understand EVERY line:
package com.amazon.epicbackendtriggers.lambda.HotwHelper;
// β This file is in the HotwHelper package
import com.amazon.epicbackendtriggers.lambda.eapDetailsHelper.EAPInputDetails;
// β Import EAPInputDetails from another package (we use it below)
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
// β Jackson annotations for JSON control
import java.util.List;
// β Standard Java List
import lombok.Builder;
import lombok.Data;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
// β Lombok annotations
// βββββββββββββββββββββββββββββββββββββββββββββ
// CLASS LEVEL ANNOTATIONS:
@JsonIgnoreProperties(ignoreUnknown = true)
// β If JSON has fields not in this class β ignore them (don't crash)
@Data
// β Lombok: generate getters, setters, equals, hashCode, toString
@Jacksonized
// β Lombok+Jackson: make @Builder work with JSON deserialization
@Builder
// β Lombok: generate builder pattern
@JsonInclude(JsonInclude.Include.NON_NULL)
// β Jackson: don't include null fields when converting to JSON
// βββββββββββββββββββββββββββββββββββββββββββββ
public class HotwExecutionAllDetailsInput {
@JsonProperty("FleetId") // JSON key is "FleetId"
private String fleetId; // Java field is "fleetId"
@JsonProperty("ServiceId")
private String serviceId;
@JsonProperty("RunId")
private int runId; // primitive int (cannot be null)
@JsonProperty("EventId")
private String eventId;
@JsonProperty("Status")
private String status; // "Success", "Fail", "PartialSuccess"
@JsonProperty("Reasoning")
private String reasoning; // explanation if something failed
@JsonProperty("HotwExecutionDetails")
private HotwExecutionDetails hotwExecutionDetails;
// β nested object (defined as inner class below)
@JsonProperty("CapacityOverrideDetails")
private List<CapacityOverrideDetails> capacityOverrideDetails;
// β list of capacity override objects
@JsonProperty("FulfillmentDetails")
private List<FulfillmentDetail> fulfillmentDetails;
// β list of fulfillment details
@JsonProperty("ASGDetails")
List<EAPInputDetails.ASGDetail> asgDetailsList;
// β list of Auto Scaling Group details (from imported class)
// βββββββββββββββββββββββββββββββββββββββββββββ
// INNER CLASS: HotwExecutionDetails
// Nested inside HotwExecutionAllDetailsInput
// βββββββββββββββββββββββββββββββββββββββββββββ
@Data
@SuperBuilder // β uses @SuperBuilder not @Builder because
// it might be used in inheritance scenarios
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@Jacksonized
public static class HotwExecutionDetails { // β "static" = doesn't need outer class instance
@JsonProperty("PeakTPM")
private Integer peakTPM; // Integer (capital I) β can be null
@JsonProperty("BAUTPM")
private Integer bauTPM;
@JsonProperty("CTPeakFactor")
private Double ctPeakFactor; // Double (capital D) β can be null
@JsonProperty("BufferFactor")
private Double bufferFactor;
@JsonProperty("HostThroughPutTPM")
private Integer hostThroughputTpm;
@JsonProperty("RequiredHosts")
private Integer requiredHosts;
@JsonProperty("HostsPresentInApollo")
private Integer hostsPresentInApollo;
// ... more fields ...
}
// βββββββββββββββββββββββββββββββββββββββββββββ
// INNER CLASS: CapacityOverrideDetails
// βββββββββββββββββββββββββββββββββββββββββββββ
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@Jacksonized
public static class CapacityOverrideDetails {
@JsonProperty("CapacityOverrideName")
private String capacityOverrideName; // name of the SPCO
@JsonProperty("StartTime")
private String startTime; // when SPCO starts
@JsonProperty("OverrideValue")
private Integer overrideValue; // how many hosts requested
@JsonProperty("PlaceOrderIfRequired")
private Boolean placeOrderIfRequired; // should hardware be ordered?
@JsonProperty("OverrideType")
private String overrideType; // type of override (Standard, etc.)
// ... more fields ...
}
// βββββββββββββββββββββββββββββββββββββββββββββ
// INNER CLASS: FulfillmentDetail
// βββββββββββββββββββββββββββββββββββββββββββββ
@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@Jacksonized
public static class FulfillmentDetail {
@JsonProperty("AutoScalingGroupName")
private String autoScalingGroupName;
@JsonProperty("FulfillmentId")
private String fulfillmentID; // unique ID for this fulfillment
@JsonProperty("FulfillmentStatus")
private String fulfillmentStatus; // PENDING, COMPLETE, etc.
@JsonProperty("FulfillmentValue")
private int fulfillmentValue; // number of hosts fulfilled
@JsonProperty("IsEmergent")
private boolean isEmergent; // was this an emergency order?
}
// βββββββββββββββββββββββββββββββββββββββββββββ
// ENUM: StatusOfHOTW (defined inside the class)
// βββββββββββββββββββββββββββββββββββββββββββββ
public enum StatusOfHOTW {
SUCCESS("Success"),
FAIL("Fail"),
PARTIAL_SUCCESS("PartialSuccess");
private final String status; // each enum has a string value
StatusOfHOTW(String value) { // constructor for enum
this.status = value;
}
public String getStatus() { // get the string value
return status;
}
}
}
How this class is CREATED in code:
// In HotwUpscalingHelper.buildHotwExecutionAllDetailsInput():
HotwExecutionAllDetailsInput.HotwExecutionDetails executionDetails =
HotwExecutionAllDetailsInput.HotwExecutionDetails.builder()
// β outer class β inner class
.homogeneous(CommonUtil.isFleetHomogeneous(sortedMap))
.preferredHostType(hostType)
.hostDistribution(JsonUtil.toJson(sortedMap))
.fleetVersionId(fleetVersionId)
.user(user)
.build(); // β create the HotwExecutionDetails object
HotwExecutionAllDetailsInput input = HotwExecutionAllDetailsInput.builder()
.fleetId(fleetId)
.serviceId(serviceId)
.eventId(eventId)
.runId(Integer.valueOf(runId)) // String β Integer
.hotwExecutionDetails(executionDetails) // set the nested object
.capacityOverrideDetails(new ArrayList<>()) // start with empty list
.fulfillmentDetails(new ArrayList<>())
.asgDetailsList(new ArrayList<>())
.build(); // β create the full input object
π― Layer 3 Summary
| Library | What it does | Key annotations |
|---|---|---|
| Lombok | Generates repetitive code | @Data, @Builder, @Jacksonized, @AllArgsConstructor, @SuperBuilder |
| Jackson | Java β JSON conversion | @JsonProperty, @JsonIgnoreProperties, @JsonInclude |
| AWS SNS SDK | Publish messages to topics | AmazonSNS, PublishRequest, MessageAttributeValue |
| AWS SQS SDK | Send messages to queues | AmazonSQS, SendMessageRequest |
| AWS Lambda | Lambda runtime utilities | Context, LambdaLogger |
β Continue to Layer 4: HotwHelper Deep Dive