Spring Content - The better way to save and serve files and images with Spring Boot

Spring Content - The better way to save and serve files and images with Spring Boot

Table Of Contents

Save images directly into the DB

Recently I added a feature to my web app where my customer could upload images and display them inside the app.

At first, I stored them directly into my Postgres Database as ByteArray inside a JPA Entity with a @Lob annotation

class Picture(
    val name: String?,
    val type: String?,
    val data: ByteArray

I then returned them as Byte64 encoded string inside my JSON API Response.


It was working fine in my local dev environment, but as soon as I pushed it to prod I encountered an error where my whole JSON structure needed for my frontend wasn't displayed properly. The Byte64 encoded image string just stopped in the middle.

I cloned the prod db locally into a Postgres container and then the issue occured also locally.

HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext@2555100f<rs=HikariProxyResultSet@1189576917 wrapping org.postgresql.jdbc.PgResultSet@545635a4>

After some 2 am debugging I got a JacksonMapping exception and found the error on stackoverflow.

I thought the solution was too complex and looked for a simpler way.


  1. Lessons: Always test with an environment as similar as production. I can recommend testcontainers for easier integration tests.

  2. Lesson: Use a database versioning tool such as liquibase instead of spring.jpa.hibernate.ddl-auto = update.

With h2 the @Lob was correctly saved as bytearray. With postgres the type was assumed to be oid which caused a issue mapping between @Lob and the bytea column type.

This was fixed with the hibernate Annotation

@Type(type = "org.hibernate.type.MaterializedBlobType")

  1. Lesson: Don't serve images directly as Base64 encoded string.

Instead, use a separate controller and serve the image data raw.

Serialize the entity which stores the data and the metadata.

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator::class, property = "id")

This avoids huge JSON responses and slowdowns to your website.

Spring Content to the Rescue

Then I discovered spring-content which looked like a better alternative than saving them directly to a db, as I could switch between db, filesystem, AWS S3, and MongoDB GridFs by just switching the dependency.

To use Spring Content you just need to add the dependency in your build.gradle.kts


Add the @ContentId annotation to your file entity. In my Case I replaced:

    @Type(type = "org.hibernate.type.MaterializedBlobType")
    val data: ByteArray


@ContentId var imageId: String

Create a ContentStore

interface PictureContentStore : ContentStore<Picture, String>

In your method where you save the files you need to use the contentstore.setContent() method. It is important to first save the entity.

val picture = pictureRepository.save(Picture(pictureFile.originalFilename, pictureFile.contentType,null))
pictureContentStore.setContent(picture, pictureFile.inputStream)

Add the handling of pictures to your file entity

class PictureController(
    val pictureService: PictureService,
    val pictureContentStore: PictureContentStore,
) {
    fun getById(
        @PathVariable id: Long,
    ): ResponseEntity<InputStreamResource> {
            ?.let { pic ->
                val content = pictureContentStore.getContent(pic)
                val inputStreamResource = InputStreamResource(content)

                return ResponseEntity.ok()
        throw NoSuchElementException("There is no Image with the corresponding id")

and you are done.

You can look up my source code on Github.

This is my first project as a freelancer. Any suggestions or feedback is very welcome