diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp
index 114348a6..426e3fcd 100644
--- a/src/android/camera_device.cpp
+++ b/src/android/camera_device.cpp
@@ -772,53 +772,6 @@ void CameraDevice::setCallbacks(const camera3_callback_ops_t *callbacks)
 	callbacks_ = callbacks;
 }
 
-std::tuple<uint32_t, uint32_t> CameraDevice::calculateStaticMetadataSize()
-{
-	/*
-	 * \todo Keep this in sync with the actual number of entries.
-	 * Currently: 63 entries, 1014 bytes of static metadata
-	 */
-	uint32_t numEntries = 63;
-	uint32_t byteSize = 1014;
-
-	// do i need to add for entries in the available keys?
-	// +1, +4 for EDGE_AVAILABLE_EDGE_MODES
-	// +1, +4 for LENS_INFO_AVAILABLE_FILTER_DENSITIES
-	// +1, +4 for BLACK_LEVEL_PATTERN
-	// +1, +4 for TONEMAP_AVAILABLE_TONE_MAP_MODES
-	// +1, +4 for TONEMAP_MAX_CURVE_POINTS
-	// +4x9 = 36 for the new result tags
-
-	// +36 for new request keys
-
-	/*
-	 * Calculate space occupation in bytes for dynamically built metadata
-	 * entries.
-	 *
-	 * Each stream configuration entry requires 48 bytes:
-	 * 4 32bits integers for ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS
-	 * 4 64bits integers for ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS
-	 */
-	byteSize += streamConfigurations_.size() * 48;
-
-	/*
-	 * 2 32bits integers for each HAL_PIXEL_FORMAT_BLOB for thumbnail sizes
-	 * 2 32bits integers for the (0, 0) thumbnail size
-	 *
-	 * This is a worst case estimates as different configurations with the
-	 * same aspect ratio will generate the same size.
-	 */
-	for (const auto &entry : streamConfigurations_) {
-		if (entry.androidFormat != HAL_PIXEL_FORMAT_BLOB)
-			continue;
-
-		byteSize += 8;
-	}
-	byteSize += 8;
-
-	return std::make_tuple(numEntries, byteSize);
-}
-
 /*
  * Return static information for the camera.
  */
@@ -827,15 +780,7 @@ const camera_metadata_t *CameraDevice::getStaticMetadata()
 	if (staticMetadata_)
 		return staticMetadata_->get();
 
-	/*
-	 * The here reported metadata are enough to implement a basic capture
-	 * example application, but a real camera implementation will require
-	 * more.
-	 */
-	uint32_t numEntries;
-	uint32_t byteSize;
-	std::tie(numEntries, byteSize) = calculateStaticMetadataSize();
-	staticMetadata_ = std::make_unique<CameraMetadata>(numEntries, byteSize);
+	staticMetadata_ = std::make_unique<CameraMetadata>(32, 512);
 	if (!staticMetadata_->isValid()) {
 		LOG(HAL, Error) << "Failed to allocate static metadata";
 		staticMetadata_.reset();
@@ -1757,13 +1702,13 @@ std::unique_ptr<CameraMetadata> CameraDevice::requestTemplateVideo()
 				  &entry);
 
 	uint8_t edgeMode = ANDROID_EDGE_MODE_FAST;
-	previewTemplate->updateEntry(ANDROID_EDGE_MODE, &edgeMode, 1);
+	previewTemplate->addEntry(ANDROID_EDGE_MODE, &edgeMode, 1);
 
 	/*
 	 * Assume the AE_AVAILABLE_TARGET_FPS_RANGE static metadata
 	 * has been assembled as {{min, max} {max, max}}.
 	 */
-	previewTemplate->updateEntry(ANDROID_CONTROL_AE_TARGET_FPS_RANGE,
+	previewTemplate->addEntry(ANDROID_CONTROL_AE_TARGET_FPS_RANGE,
 				     entry.data.i32 + 2, 2);
 
 	return previewTemplate;
@@ -1780,17 +1725,17 @@ std::unique_ptr<CameraMetadata> CameraDevice::requestTemplateStill()
 	 * HIGH_QUALITY.
 	 */
 	uint8_t noiseReduction = ANDROID_NOISE_REDUCTION_MODE_HIGH_QUALITY;
-	previewTemplate->updateEntry(ANDROID_NOISE_REDUCTION_MODE,
+	previewTemplate->addEntry(ANDROID_NOISE_REDUCTION_MODE,
 				     &noiseReduction, 1);
 
 	uint8_t edgeMode = ANDROID_EDGE_MODE_HIGH_QUALITY;
-	previewTemplate->updateEntry(ANDROID_EDGE_MODE, &edgeMode, 1);
+	previewTemplate->addEntry(ANDROID_EDGE_MODE, &edgeMode, 1);
 
 	uint8_t shadingMode = ANDROID_SHADING_MODE_HIGH_QUALITY;
-	previewTemplate->updateEntry(ANDROID_SHADING_MODE, &shadingMode, 1);
+	previewTemplate->addEntry(ANDROID_SHADING_MODE, &shadingMode, 1);
 
 	uint8_t tonemapMode = ANDROID_TONEMAP_MODE_HIGH_QUALITY;
-	previewTemplate->updateEntry(ANDROID_TONEMAP_MODE, &tonemapMode, 1);
+	previewTemplate->addEntry(ANDROID_TONEMAP_MODE, &tonemapMode, 1);
 
 	return previewTemplate;
 }
@@ -1802,16 +1747,16 @@ std::unique_ptr<CameraMetadata> CameraDevice::requestTemplateManual()
 		return nullptr;
 
 	uint8_t controlMode = ANDROID_CONTROL_MODE_OFF;
-	previewTemplate->updateEntry(ANDROID_CONTROL_MODE, &controlMode, 1);
+	previewTemplate->addEntry(ANDROID_CONTROL_MODE, &controlMode, 1);
 
 	uint8_t aeMode = ANDROID_CONTROL_AE_MODE_OFF;
-	previewTemplate->updateEntry(ANDROID_CONTROL_AE_MODE, &aeMode, 1);
+	previewTemplate->addEntry(ANDROID_CONTROL_AE_MODE, &aeMode, 1);
 
 	uint8_t awbMode = ANDROID_CONTROL_AWB_MODE_OFF;
-	previewTemplate->updateEntry(ANDROID_CONTROL_AWB_MODE, &awbMode, 1);
+	previewTemplate->addEntry(ANDROID_CONTROL_AWB_MODE, &awbMode, 1);
 
 	uint8_t edgeMode = ANDROID_EDGE_MODE_OFF;
-	previewTemplate->updateEntry(ANDROID_EDGE_MODE, &edgeMode, 1);
+	previewTemplate->addEntry(ANDROID_EDGE_MODE, &edgeMode, 1);
 
 	/* \todo get this from available filter densities */
 	float filterDensity = 0.0f;
@@ -1867,7 +1812,7 @@ const camera_metadata_t *CameraDevice::constructDefaultRequestSettings(int type)
 		return nullptr;
 	}
 
-	requestTemplate->updateEntry(ANDROID_CONTROL_CAPTURE_INTENT,
+	requestTemplate->addEntry(ANDROID_CONTROL_CAPTURE_INTENT,
 				     &captureIntent, 1);
 
 	requestTemplates_[type] = std::move(requestTemplate);
diff --git a/src/android/camera_device.h b/src/android/camera_device.h
index 8edbcdfd..88aab012 100644
--- a/src/android/camera_device.h
+++ b/src/android/camera_device.h
@@ -98,7 +98,6 @@ private:
 	std::vector<libcamera::Size>
 	getRawResolutions(const libcamera::PixelFormat &pixelFormat);
 
-	std::tuple<uint32_t, uint32_t> calculateStaticMetadataSize();
 	libcamera::FrameBuffer *createFrameBuffer(const buffer_handle_t camera3buffer);
 	void notifyShutter(uint32_t frameNumber, uint64_t timestamp);
 	void notifyError(uint32_t frameNumber, camera3_stream_t *stream);
diff --git a/src/android/camera_metadata.cpp b/src/android/camera_metadata.cpp
index 6f1bcdbe..b7abea96 100644
--- a/src/android/camera_metadata.cpp
+++ b/src/android/camera_metadata.cpp
@@ -63,52 +63,73 @@ bool CameraMetadata::getEntry(uint32_t tag, camera_metadata_ro_entry_t *entry) c
 	return true;
 }
 
-bool CameraMetadata::addEntry(uint32_t tag, const void *data, size_t count)
+bool CameraMetadata::resize(size_t count, size_t size)
 {
 	if (!valid_)
 		return false;
 
-	if (!add_camera_metadata_entry(metadata_, tag, data, count))
-		return true;
-
-	const char *name = get_camera_metadata_tag_name(tag);
-	if (name)
-		LOG(CameraMetadata, Error)
-			<< "Failed to add tag " << name;
-	else
-		LOG(CameraMetadata, Error)
-			<< "Failed to add unknown tag " << tag;
-
-	valid_ = false;
+	size_t currentEntryCount = get_camera_metadata_entry_count(metadata_);
+	size_t currentEntryCapacity = get_camera_metadata_entry_capacity(metadata_);
+	size_t newEntryCapacity = currentEntryCapacity < currentEntryCount + count ?
+				  currentEntryCapacity * 2 : currentEntryCapacity;
+
+	size_t currentDataCount = get_camera_metadata_data_count(metadata_);
+	size_t currentDataCapacity = get_camera_metadata_data_capacity(metadata_);
+	size_t newDataCapacity = currentDataCapacity < currentDataCount + size ?
+				 currentDataCapacity * 2 : currentDataCapacity;
+
+	if (newEntryCapacity > currentEntryCapacity ||
+	    newDataCapacity > currentDataCapacity) {
+		camera_metadata_t *oldMetadata = metadata_;
+		metadata_ = allocate_camera_metadata(newEntryCapacity, newDataCapacity);
+		if (!metadata_) {
+			metadata_ = oldMetadata;
+			return false;
+		}
+
+		append_camera_metadata(metadata_, oldMetadata);
+		free_camera_metadata(oldMetadata);
+	}
 
-	return false;
+	return true;
 }
 
-bool CameraMetadata::updateEntry(uint32_t tag, const void *data, size_t count)
+void CameraMetadata::addEntry(uint32_t tag, const void *data, size_t count,
+			      size_t sizeofT)
 {
 	if (!valid_)
-		return false;
+		return;
 
 	camera_metadata_entry_t entry;
 	int ret = find_camera_metadata_entry(metadata_, tag, &entry);
 	if (ret) {
-		const char *name = get_camera_metadata_tag_name(tag);
-		LOG(CameraMetadata, Error)
-			<< "Failed to update tag "
-			<< (name ? name : "<unknown>") << ": not present";
-		return false;
-	}
+		if (!resize(1, count * sizeofT)) {
+			LOG(CameraMetadata, Error) << "Failed to resize";
+			valid_ = false;
+			return;
+		}
+
+		if (!add_camera_metadata_entry(metadata_, tag, data, count))
+			return;
 
-	ret = update_camera_metadata_entry(metadata_, entry.index, data,
-					   count, nullptr);
-	if (ret) {
 		const char *name = get_camera_metadata_tag_name(tag);
-		LOG(CameraMetadata, Error)
-			<< "Failed to update tag " << (name ? name : "<unknown>");
-		return false;
+		LOG(CameraMetadata, Error) << "Failed to add tag " << (name ? name : "<unknown>");
+
+		valid_ = false;
+
+		return;
 	}
 
-	return true;
+	if (!update_camera_metadata_entry(metadata_, entry.index, data,
+					  count, nullptr))
+		return;
+
+	const char *name = get_camera_metadata_tag_name(tag);
+	LOG(CameraMetadata, Error) << "Failed to update tag " << (name ? name : "<unknown>");
+
+	valid_ = false;
+
+	return;
 }
 
 camera_metadata_t *CameraMetadata::get()
diff --git a/src/android/camera_metadata.h b/src/android/camera_metadata.h
index d653e2f0..c4c91112 100644
--- a/src/android/camera_metadata.h
+++ b/src/android/camera_metadata.h
@@ -11,6 +11,8 @@
 
 #include <system/camera_metadata.h>
 
+#include <libcamera/span.h>
+
 class CameraMetadata
 {
 public:
@@ -23,9 +25,28 @@ public:
 	CameraMetadata &operator=(const CameraMetadata &other);
 
 	bool isValid() const { return valid_; }
+	bool resize(size_t count, size_t size);
 	bool getEntry(uint32_t tag, camera_metadata_ro_entry_t *entry) const;
-	bool addEntry(uint32_t tag, const void *data, size_t data_count);
-	bool updateEntry(uint32_t tag, const void *data, size_t data_count);
+
+	template<typename T>
+	void addEntry(uint32_t tag, const T &data)
+	{
+		addEntry(tag, &data, 1, sizeof(T));
+	}
+
+	template<typename T>
+	void addEntry(uint32_t tag, libcamera::Span<const T> &data)
+	{
+		addEntry(tag, data.data(), data.size(), sizeof(T));
+	}
+
+	template<typename T>
+	void addEntry(uint32_t tag, const T *data, size_t count)
+	{
+		addEntry(tag, data, count, sizeof(T));
+	}
+
+	void addEntry(uint32_t tag, const void *data, size_t count, size_t sizeofT);
 
 	camera_metadata_t *get();
 	const camera_metadata_t *get() const;
diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp
index 237fb318..4c6e5eb2 100644
--- a/src/android/jpeg/post_processor_jpeg.cpp
+++ b/src/android/jpeg/post_processor_jpeg.cpp
@@ -15,6 +15,7 @@
 #include "exif.h"
 
 #include <libcamera/formats.h>
+#include <libcamera/span.h>
 
 #include "libcamera/internal/log.h"
 
@@ -103,7 +104,7 @@ int PostProcessorJpeg::process(const FrameBuffer &source,
 	ret = requestMetadata.getEntry(ANDROID_JPEG_ORIENTATION, &entry);
 
 	const uint32_t jpegOrientation = ret ? *entry.data.i32 : 0;
-	resultMetadata->addEntry(ANDROID_JPEG_ORIENTATION, &jpegOrientation, 1);
+	resultMetadata->addEntry(ANDROID_JPEG_ORIENTATION, jpegOrientation);
 	exif.setOrientation(jpegOrientation);
 
 	exif.setSize(streamSize_);
@@ -132,7 +133,7 @@ int PostProcessorJpeg::process(const FrameBuffer &source,
 	if (ret) {
 		exif.setGPSDateTimestamp(*entry.data.i64);
 		resultMetadata->addEntry(ANDROID_JPEG_GPS_TIMESTAMP,
-					 entry.data.i64, 1);
+					 *entry.data.i64);
 	}
 
 	ret = requestMetadata.getEntry(ANDROID_JPEG_THUMBNAIL_SIZE, &entry);
@@ -143,7 +144,7 @@ int PostProcessorJpeg::process(const FrameBuffer &source,
 
 		ret = requestMetadata.getEntry(ANDROID_JPEG_THUMBNAIL_QUALITY, &entry);
 		uint8_t quality = ret ? *entry.data.u8 : 95;
-		resultMetadata->addEntry(ANDROID_JPEG_THUMBNAIL_QUALITY, &quality, 1);
+		resultMetadata->addEntry(ANDROID_JPEG_THUMBNAIL_QUALITY, quality);
 
 		if (thumbnailSize != Size(0, 0)) {
 			std::vector<unsigned char> thumbnail;
@@ -152,14 +153,15 @@ int PostProcessorJpeg::process(const FrameBuffer &source,
 				exif.setThumbnail(thumbnail, Exif::Compression::JPEG);
 		}
 
-		resultMetadata->addEntry(ANDROID_JPEG_THUMBNAIL_SIZE, data, 2);
+		resultMetadata->addEntry(ANDROID_JPEG_THUMBNAIL_SIZE,
+					 Span<const int32_t>{data, 2});
 	}
 
 	ret = requestMetadata.getEntry(ANDROID_JPEG_GPS_COORDINATES, &entry);
 	if (ret) {
 		exif.setGPSLocation(entry.data.d);
 		resultMetadata->addEntry(ANDROID_JPEG_GPS_COORDINATES,
-					 entry.data.d, 3);
+					 Span<const double>{entry.data.d, 3});
 	}
 
 	ret = requestMetadata.getEntry(ANDROID_JPEG_GPS_PROCESSING_METHOD, &entry);
@@ -175,7 +177,7 @@ int PostProcessorJpeg::process(const FrameBuffer &source,
 
 	ret = requestMetadata.getEntry(ANDROID_JPEG_QUALITY, &entry);
 	const uint8_t quality = ret ? *entry.data.u8 : 95;
-	resultMetadata->addEntry(ANDROID_JPEG_QUALITY, &quality, 1);
+	resultMetadata->addEntry(ANDROID_JPEG_QUALITY, quality);
 
 	int jpeg_size = encoder_->encode(source, destination->plane(0),
 					 exif.data(), quality);
@@ -193,7 +195,7 @@ int PostProcessorJpeg::process(const FrameBuffer &source,
 	blob->jpeg_size = jpeg_size;
 
 	/* Update the JPEG result Metadata. */
-	resultMetadata->addEntry(ANDROID_JPEG_SIZE, &jpeg_size, 1);
+	resultMetadata->addEntry(ANDROID_JPEG_SIZE, jpeg_size);
 
 	return 0;
 }
