February 21, 2025

Fixing "WaitTimeSeconds is Invalid" Error in Amazon SQS

Introduction

Amazon Simple Queue Service (SQS) is like a postbox for messages in the cloud. It helps different parts of a system talk to each other by sending and receiving messages safely. However, sometimes, you might see an error like this:

"pool-12-thread-1" software.amazon.awssdk.services.sqs.model.SqsException: Value 120 for parameter WaitTimeSeconds is invalid. Reason: Must be >= 0 and <= 20, if provided. (Service: Sqs, Status Code: 400, Request ID: 6778dcd6-b3e8-59ca-a981-bce42253312d)

This error happens when the WaitTimeSeconds setting is greater than 20, which is not allowed. Let's break it down so even a child can understand and see how to fix it.


Understanding the Error

What is WaitTimeSeconds?

Think of WaitTimeSeconds like waiting at a bus stop. If you set it to 0, it's like checking for a bus and leaving immediately if there isn’t one (short polling). If you set it to a number between 1 and 20, it means you wait for some time before deciding no bus is coming (long polling). But if you try to wait more than 20 seconds, SQS says, “That’s too long!” and throws an error.

Why Does This Happen?

If the system tries to wait longer than 20 seconds, Amazon SQS stops it because that’s the maximum waiting time allowed per request.

Common reasons include:

  • Wrong settings in your application code.
  • Misunderstanding of polling rules (thinking you can wait longer than allowed).
  • Forgetting to set a valid value and using a default that is too high.

When and How to Use WaitTimeSeconds

When Should You Use It?

  • If you want to check for new messages quickly, set WaitTimeSeconds = 0 (short polling).
  • If you want to reduce unnecessary requests and save money, set WaitTimeSeconds between 1-20 (long polling).
  • If your system does not need real-time responses, use the maximum 20 seconds to lower API costs and reduce load on your application.

How to Use It Correctly

Example in AWS SDK for Java (v2):

import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest;

ReceiveMessageRequest receiveMessageRequest = ReceiveMessageRequest.builder()
    .queueUrl(queueUrl)
    .waitTimeSeconds(20) // ✅ Must be between 0 and 20
    .maxNumberOfMessages(10)
    .build();

Recommended Settings for Best Performance

Scenario Recommended WaitTimeSeconds Why?
Real-time applications 0-5 Faster response but may increase API calls.
Standard polling (normal) 10-15 Balanced approach for efficiency.
Batch processing (not urgent) 20 Reduces cost by minimizing API calls.

Fixing the Issue

1. Use a Valid WaitTimeSeconds Value

Make sure it’s between 0 and 20 to avoid errors.

2. Loop If You Need a Longer Delay

If you need more than 20 seconds, use a loop instead of setting an invalid value.

while (true) {
    ReceiveMessageRequest receiveMessageRequest = ReceiveMessageRequest.builder()
        .queueUrl(queueUrl)
        .waitTimeSeconds(20) // Maximum allowed value
        .maxNumberOfMessages(10)
        .build();

    sqsClient.receiveMessage(receiveMessageRequest);
}

3. Set Default Long Polling in Queue Settings

If you want all consumers to wait for a specific time, set the Receive Message Wait Time in the queue settings (Amazon SQS Console → Queue → Edit → Receive Message Wait Time).


Best Practices for Using Long Polling in SQS

  • Use long polling (WaitTimeSeconds > 0) to reduce API requests and costs.
  • Set WaitTimeSeconds to 20 for best efficiency and fewer requests.
  • Use batch processing (maxNumberOfMessages > 1) to optimize performance.
  • Monitor with AWS CloudWatch to track queue performance and adjust settings.
  • Implement retries with exponential backoff to handle failures properly.

Common Problems and Solutions

1. High API Costs Due to Short Polling

  • If WaitTimeSeconds=0, you’ll make too many API requests.
  • Solution: Set WaitTimeSeconds to at least 10-20 seconds.

2. Messages Take Too Long to Appear

  • If messages don’t appear, another system might be processing them due to the visibility timeout setting.
  • Solution: Check and adjust visibility timeout if necessary.

3. System Stops Receiving Messages Suddenly

  • If your app isn’t receiving messages, check if it’s not processing them fast enough.
  • Solution: Increase maxNumberOfMessages and ensure enough workers are running.

Further Reading


Conclusion

The "WaitTimeSeconds is invalid" error in Amazon SQS happens when you set an invalid value above 20. To fix it:

  1. Set WaitTimeSeconds between 0-20.
  2. Use a loop if you need longer delays.
  3. Configure queue settings for default long polling.
  4. Follow best practices for cost and performance efficiency.

By following these recommendations, you can use SQS more effectively, reduce API costs, and avoid common pitfalls. Happy coding! 🚀

February 18, 2025

Custom Annotation in Spring Boot: Restricting Age Below 18

 When making apps, sometimes we need to stop kids under 18 from signing up. Instead of writing the same rule everywhere, we can make a special tag (annotation) to check age easily. Let's learn how to do it step by step!

Why Use Custom Annotations?

Spring Boot has built-in checks like @NotNull (not empty) and @Size (length), but not for age. Instead of writing the same age-checking code again and again, we create a custom annotation that we can use anywhere in the app.

Steps to Create an Age Validator

Step 1: Create the Annotation

This is like making a new sticker that says "Check Age" which we can put on our data fields.

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Constraint(validatedBy = AgeValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MinAge {
    int value() default 18;
    String message() default "You must be at least {value} years old";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Understanding the Annotations Used

  • @Constraint(validatedBy = AgeValidator.class): Links the annotation to the AgeValidator class, which contains the logic to validate the age.
  • @Target({ElementType.FIELD, ElementType.PARAMETER}): Specifies where we can use this annotation. Options include:
    • ElementType.FIELD: Can be applied to fields in a class.
    • ElementType.PARAMETER: Can be used on method parameters.
    • Other options: METHOD, TYPE, ANNOTATION_TYPE, etc.
  • @Retention(RetentionPolicy.RUNTIME): Defines when the annotation is available. Options include:
    • RetentionPolicy.RUNTIME: The annotation is accessible during runtime (needed for validation).
    • RetentionPolicy.CLASS: Available in the class file but not at runtime.
    • RetentionPolicy.SOURCE: Only used in source code and discarded by the compiler.

Step 2: Write the Age Checking Logic

This part calculates the age and tells if it's 18 or more.

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.time.LocalDate;
import java.time.Period;

public class AgeValidator implements ConstraintValidator<MinAge, LocalDate> {
    private int minAge;

    @Override
    public void initialize(MinAge constraintAnnotation) {
        this.minAge = constraintAnnotation.value();
    }

    @Override
    public boolean isValid(LocalDate dob, ConstraintValidatorContext context) {
        if (dob == null) {
            return false; // No date means invalid
        }
        return Period.between(dob, LocalDate.now()).getYears() >= minAge;
    }
}

Step 3: Use the Annotation in a User Data Class

Now, we use @MinAge to check age whenever someone signs up.

import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;

public class UserDTO {
    @NotNull(message = "Please enter your birthdate")
    @MinAge(18)
    private LocalDate dateOfBirth;

    // Getters and Setters
    public LocalDate getDateOfBirth() {
        return dateOfBirth;
    }

    public void setDateOfBirth(LocalDate dateOfBirth) {
        this.dateOfBirth = dateOfBirth;
    }
}

Step 4: Apply Validation in a Controller

When a new user signs up, we check their age automatically.

import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
public class UserController {
    @PostMapping("/register")
    public ResponseEntity<String> registerUser(@RequestBody @Valid UserDTO userDTO) {
        return ResponseEntity.ok("User registered successfully");
    }
}

Step 5: Test the Validation

If someone younger than 18 tries to sign up, they will see this message:

{
  "dateOfBirth": "You must be at least 18 years old"
}

Making Sure Name is Lowercase

Sometimes, we want names to be stored in lowercase automatically. There are two ways to do this:

Option 1: Use @ColumnTransformer (Hibernate)

If using Hibernate, we can transform the value before saving.

import org.hibernate.annotations.ColumnTransformer;

@Entity
public class User {
    @ColumnTransformer(write = "lower(?)")
    private String name;
}

Option 2: Custom Annotation for Lowercase

If we want to ensure lowercase format, we can create a custom annotation.

Step 1: Create the Annotation

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Constraint(validatedBy = LowercaseValidator.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lowercase {
    String message() default "Must be lowercase";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Step 2: Create the Validator

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class LowercaseValidator implements ConstraintValidator<Lowercase, String> {
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value != null && value.equals(value.toLowerCase());
    }
}

Step 3: Use the Annotation

public class UserDTO {
    @Lowercase
    private String name;
}

Recommended Approach

  • If using Hibernate, @ColumnTransformer(write = "lower(?)") is simple and works well.
  • If working with validations, a custom @Lowercase annotation ensures that input is already correct.
  • A hybrid approach: Apply both for best consistency.

Conclusion

By making a custom @MinAge annotation, we ensure kids under 18 cannot register. Similarly, using a @Lowercase annotation or Hibernate’s built-in transformation helps maintain data consistency. These techniques keep our code clean, reusable, and easy to maintain.

Hope this helps! Happy coding! 🚀