<!-- Love localstack? Please consider supporting our collective:
👉  https://opencollective.com/localstack/donate -->

Found a pagination bug with localstack's s3. Primarily against s3Client.listNextBatchOfObjects.

    private void test() {
        s3Client.putObject(s3BucketName, "key1", "content");
        s3Client.putObject(s3BucketName, "key2", "content");
        s3Client.putObject(s3BucketName, "key3", "content");

        ListObjectsRequest listObjectsRequest = new ListObjectsRequest()
                .withBucketName(s3BucketName)
                .withPrefix("")
                .withDelimiter("/")
                .setMaxKeys(1); // 1 Key per request

        ObjectListing objectListing = s3Client.listObjects(listObjectsRequest);
        List<SomeObject> someObjList = createEmptyList();
        someObjList.addAll(mapFilesToSomeObject(objectListing)); // puts at least 1 item into the list

        while (objectListing.isTruncated()) {
            objectListing = s3Client.listNextBatchOfObjects(objectListing);
            someObjList.addAll(mapFilesToSomeObject(objectListing));
        }
        assertEquals(3, someObjList.size());
    }

    private List<SomeObject> mapFilesToSomeObject(ObjectListing objectListing) {
        return objectListing.getObjectSummaries()
            .stream()
            .map(S3ObjectSummary::getKey)
            .map(x -> this.toSomeObject(x)) // some deserialization method
            .collect(Collectors.toList());
    }

This while loop will only get the second item in its first loop. It still enters the loop once more to grab the third item since the condition is still true, but the 2nd call of listNextBatchOfObjects in that very loop returns an empty list, thus failing to grab that third item. I confirmed that listNextBatchOfObjects retrieves the "second page" only against localstack, with changing the amount of items and page sizes. On AWS s3, this works as expected.

Refer to s3Client.listNextBatchOfObjects:
https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/AmazonS3Client.html#listNextBatchOfObjects-com.amazonaws.services.s3.model.ObjectListing-
""" Class to handle putting with urllib """
    def __init__(self, *args, **kwargs):
        return Request.__init__(self, *args, **kwargs)
    def get_method(self, *args, **kwargs):
        return 'PUT'
class S3ListenerTest(unittest.TestCase):
    def setUp(self):
        self.s3_client = aws_stack.connect_to_service('s3')
        self.sqs_client = aws_stack.connect_to_service('sqs')
    def test_create_bucket_via_host_name(self):
        body = """<?xml version="1.0" encoding="UTF-8"?>
            <CreateBucketConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
                <LocationConstraint>eu-central-1</LocationConstraint>
            </CreateBucketConfiguration>"""
        headers = aws_stack.mock_aws_request_headers('s3')
        bucket_name = 'test-%s' % short_uid()
        headers['Host'] = '%s.s3.amazonaws.com' % bucket_name
        response = requests.put(config.TEST_S3_URL, data=body, headers=headers, verify=False)
        self.assertEquals(response.status_code, 200)
        response = self.s3_client.get_bucket_location(Bucket=bucket_name)
        self.assertEqual(response['ResponseMetadata']['HTTPStatusCode'], 200)
        self.assertIn('LocationConstraint', response)
    def test_bucket_policy(self):