Lombok, Jackson, AWS SDK β€” The magic behind the HOTW code


Table of Contents

  1. What are Annotations?
  2. Lombok β€” Stop Writing Boilerplate
  3. Jackson β€” Java ↔ JSON Conversion
  4. AWS SDK β€” Talking to AWS Services
  5. 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