Search code examples
javaimagebase64wildfly-8javax.imageio

ImageIO.read(URL) takes forever loading an image


I'm implementing an REST-API with JavaEE-Techniques which is going to be accessed by mobile apps and answering with some JSON-Data in response to their requests. Everything works fine so far until we noticed a query taking unusual long. At first I thought it is a 'complex' database-query which is running so long, but after some debugging it pointed out that accessing the thumbnailpicture of retrieved data by the ImageIO.read(URL) method is the cause for the request to take so long.

Via the database-query I'm retrieving a URL pointing to the (thumbnail) image data I want to retrieve. Before I'm creating the JSON-Data of the object to send it back to the client, I'm going to encode the Image as a Base64-String.

My code looks as follows (I know its not that nice at the moment, but thats the way I got it to work for now, I know I can make use of try-resource blocks and should probably convert the logging into log-level debug, and I'm going to):

public class Base64Utility
{
    public static String encodeBase64FromUrl(final URL pImageUrl, final ThumbnailPictureDbo pThumbnailPicture)
    {
        BufferedImage img = null;
        String base64EncodedImageString = null;
        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] imageBytes = null;

        try
        {
            logger.info("Starting to convert the image!");

            final Long start = System.currentTimeMillis();

            // The Sucker!
            img = ImageIO.read(pImageUrl);
            final Long imageLoadTime = System.currentTimeMillis() - start;
            logger.info("Loaded Image from url {} in: {}", pImageUrl, imageLoadTime);

            final Long start2 = System.currentTimeMillis();
            ImageIO.write(img, "png", bos);
            imageBytes = bos.toByteArray();

            final Long imageWriteTime = System.currentTimeMillis() - start2;
            logger.info("Wrote Image in: {}", imageWriteTime);
            bos.close();

            final Long start3 = System.currentTimeMillis();
            // What to return when img == null?!
            base64EncodedImageString = imageBytes != null ? Base64.getEncoder()
                .encodeToString(imageBytes) : "";

            final Long imageEncodeTime= System.currentTimeMillis() - start3;
            logger.info("Encoded Image in: {}", imageEncodeTime);

            pThumbnailPicture.setBase64EncodedImage(base64EncodedImageString);

            logger.info("Finished converting the image!");
            logger.info("Took total time: " + (System.currentTimeMillis() - start));
            return base64EncodedImageString;
        }
        catch (final IOException e)
        {
            e.printStackTrace();
        }
    return base64EncodedImageString;
}

The images are hosted on the same machine as the database is located and the code runs on. Thus the URL-param contains it's hostname, but could also be accessed via "localhost" or as a file (but it is stored with the hostname in the database. For convenience I'm not converting it, having also found the very handy ImageIO.read(URL) method.) Thus the network shouldn't be the bottleneck either.
The very weird behaviour is, that ImageIO takes 10 - 60 Seconds accessing an image by url on the first call. Afterwards (multiple result-data in the same client-request) it takes just some 100 Milliseconds.
On a whole new request it behaves the same (first call taking abnormal long and on the following ones very short). That's the output of some logs (during one request of the client!):

Starting to convert the image!
Loaded Image from url <URL>/Logo-KF-transparent-1500-1024x992.png in: 21810
Wrote Image in: 973
Encoded Image in: 3
Finished converting the image!
Took total time: 22787

Starting to convert the image!
Loaded Image from url <URL>/IMG_1026_905.jpg in: 157
Wrote Image in: 440
Encoded Image in: 7
Finished converting the image!
Took total time: 605

Starting to convert the image!
Loaded Image from url <URL>/WorkshopS2_KaffeeFabrik_1200x800-500x300.jpg in: 23
Wrote Image in: 101
Encoded Image in: 2
Finished converting the image!
Took total time: 127

Starting to convert the image!
Loaded Image from url <URL>/kaffeezumabnehmen.jpg in: 226
Wrote Image in: 98
Encoded Image in: 4
Finished converting the image!
Took total time: 329

Starting to convert the image!
Loaded Image from url <URL>/kaffee_apa.jpg in: 12
Wrote Image in: 60
Encoded Image in: 2
Finished converting the image!
Took total time: 75

Thereby the behaviour does not depend on the image neither on the image's format, it's always the first access/ load which is taking so long! Has anyone an idea or a solution? I'm also fine with another approach to read/ load the images, this was just my first attempt and the first result of some research by myself. Maybe there is whole different and better approach to do so.

I'm calling the utility from an RequestScoped-CDI bean in this way:

@RequestScoped
public class LocationPersistenceServiceImpl implements LocationPersistenceService
{
    // Some other attributes and methods...

    @Override
    public List<LocationData> findLocations(final QueryParams[] pQueryParams)
    {
        final QueryBuilder<LocationDbo> qb = new QueryBuilder<>(pQueryParams, emf, LocationDbo.class);
        final List<LocationDbo> locationDboQueryResults = qb.getResultList();
        for (LocationDbo retrievedLocDbo : locationDboQueryResults)
        {
            retrievedLocDbo = enhanceRetrievedLocationDboMetaInformation(retrievedLocDbo);
        }
        return dboToData.convert(locationDboQueryResults);
    }

    private LocationDbo enhanceRetrievedLocationDboMetaInformation(LocationDbo pRetrievedLocDbo)
    {
        final List<PostMetaInformationDbo> postMetaInfos = pPs.retrievePostMetaInfomationsById(pRetrievedLocDbo.getPostId());
        pRetrievedLocDbo = addRetrievedLocationMetaInfosFromLocationPost(pRetrievedLocDbo, postMetaInfos);
        return pRetrievedLocDbo;
    }

    private LocationDbo addRetrievedLocationMetaInfosFromLocationPost(final LocationDbo pLocDbo, final List<PostMetaInformationDbo> pPostMeta)
    {
        for (final PostMetaInformationDbo p : pPostMeta)
        {
            // Some code...
        }
        if ((pLocDbo.getThumbnailPicture() != null) && (!StringUtils.isBlank(pLocDbo.getThumbnailPicture()
            .getGuid())))
        {
            Base64Utility.encodeBase64FromUrl(pLocDbo.getThumbnailPicture()
                .getGuid(), pLocDbo.getThumbnailPicture());
        }
        return pLocDbo;
    }
}

Another idea of mine would be to annotate the Base64Utility class as a Stateless bean and inject it into the service class. Maybe this could improve some caching/ connection procedure or support some parallel execution / threading or the like (haven't tried it yet).

Maybe someone had similar problems or sees an obvious wrongdoing while working with ImageIO and can share his knowlege.

Thank you and best regards!

P.S.: The whole code is running in an Wildlfy (8.2.Final and on Java 8) Application Server. Thus starting some Threads manually while working with ImageIO (how some posts on my research suggest) shouldn't be an great idea.


Solution

  • As it pointed out, it was not directly a problem of ImageIO, furthermore it was related to an slow/ misconfigured Apache server. Simply restarting the Apache server, which serves the images, solved my problem.