In our app there are two ways we serve images.
The first is through asset pipeline and second is fetching from database.
For the main company header logo we serve it from asset pipeline.
The gsp code looks like this:
<img src="${resource(dir: 'images', file: 'logo.png')}" />
which renders in html to:
<img src="/roadrace/assets/logo-addd262d2fffbdf8d868181896bf1698.png">
When the browser fetches this it is correctly memory cached as indicated in this request and marked by red box.
At other place where we display event image, the code look likes this:
<img src="https://.../roadrace/uploads/logos/h1FdUah7vXGqTkq.jpg?1676524906000" id="logo">
This makes get request to this controller action:
def uploads(String path) {
FileData imageData = FileData.findByPath("/${path}")
if (!imageData) {
response.sendError(404)
return
}
response.contentType = URLConnection.guessContentTypeFromName(path) ?: 'application/octet-stream'
response.getOutputStream().withCloseable {out ->
out.write(imageData.data)
out.flush()
}
}
You can see we are first fetching the image data using FileData domain which has data field which is of type byte[] which is written to response.
File data is defined as:
class FileData {
String path
byte[] data
static constraints = {
path blank: false, unique: true
data nullable: false, maxSize: 16_777_215
}
}
So here how can we add response header so that this image is also browser cached?
Since we are using images only in one place, it is best not to use cache plugins to keep the app lighter.

Modern browser engines have made cache of base64 image representations too. I discovered it two days ago while trying to build a browser cache using localStorage.
So, you can try to convert your byte array to a base64 representation. Take a look here how you can do it:
Base64 encoding in Java / Groovy
Chrome's returning base64 image from cache:
enter image description here