How to Display Attachments in BIRT Reports for IBM Maximo Application Suite (MAS 9)
Sree Bolisetti
April 29, 2026


In our previous blog, we walked through how to display images attached by field technicians directly within BIRT reports in IBM Maximo 7.6. That approach worked by reading image files directly from the server's file system using a local file path stored in the urlName field of the docinfo object.
With the move to IBM Maximo Application Suite (MAS 9), that method no longer works. MAS 9 is a cloud-native platform, and attachments are no longer stored on a local file system, they are stored in Cloud Object Storage (COS), which is S3-compatible. The urlName field now contains a COS reference (e.g. cos:doclinks/your-file.jpg) rather than a local path, so the old File() approach fails entirely.
This blog details the updated approach to fetch and render those images from S3/COS within your BIRT reports on MAS 9.
In Maximo 7.6, the urlName value pointed directly to a file on disk, for example:
/opt/IBM/SMP/maximo/doclinks/Attachments/image.jpg
The script simply read that file using java.io.File and passed the bytes to the BIRT dynamic image element — straightforward for an on-premise deployment.
In MAS 9, the same field now holds a COS URI like:
cos:doclinks/image.jpg
There is no local file to read. The image lives in an S3 bucket, and you must authenticate with AWS credentials and download the object at report runtime. The BIRT report layout itself (Dynamic Image in a table cell) remains exactly the same. Only the scripts need to change.
The solution involves two scripting areas in the BIRT report designer:
The report layout steps (creating a dataset from docinfo, binding it to a table, inserting a Dynamic Image element) remain the same as described in the original blog. If you haven't completed those steps yet, refer to steps 1–3 of the original guide first.
Same as before, create a dataset that fetches the urlName from the docinfo object, filtered by your criteria (e.g. specific inspection question or document type). The urlName column is what the scripts will use to locate the image in S3.
Construct your table, bind it to the dataset, and place an image element inside the relevant cell. Set the image source type to "Dynamic Image" — this is unchanged from Maximo 7.6.
In the report's Initialize script section, add the following. This runs once at report startup, reads the COS credentials from Maximo system properties, and creates a shared S3 client:

importPackage(Packages.com.ibm.tivoli.maximo.report.script);
importPackage(Packages.com.amazonaws.auth);
importPackage(Packages.com.amazonaws.services.s3);
importPackage(Packages.com.amazonaws.services.s3.model);
importPackage(Packages.psdi.server);
var accessKey = MXServer.getMXServer().getProperty("mxe.cosaccesskey");
var secretKey = MXServer.getMXServer().getProperty("mxe.cossecretkey");
var bucketName = MXServer.getMXServer().getProperty("mxe.cossysbucket");
// Set up AWS credentials and S3 client
var credentials = new BasicAWSCredentials(accessKey, secretKey);
var s3Client = new AmazonS3Client(credentials);
mxReportScriptContext = MXReportScriptContext.initialize(reportContext);
mxReportScriptContext.setDefaultLogLevel("DEBUG");
mxReportScriptContext.setDefaultLogFile("servicereport.log");
scriptLogger = mxReportScriptContext.getReportScriptLogger();
Note: The credentials (mxe.cosaccesskey, mxe.cossecretkey, mxe.cossysbucket) are read directly from Maximo's system properties, so there is no need to hard-code sensitive values in the report.
In the Dynamic Image element's onCreate script, add the following. This fires for each row, parses the COS URI, downloads the image, handles format conversion, applies JPEG compression, and sets display dimensions:
importPackage(Packages.javax.imageio);
importPackage(Packages.javax.imageio.stream);
importPackage(Packages.java.io);
importPackage(Packages.java.lang);
importPackage(Packages.java.awt.image);
importPackage(Packages.java.awt);
importPackage(Packages.java.net);
importPackage(Packages.com.amazonaws.auth);
importPackage(Packages.com.amazonaws.services.s3);
importPackage(Packages.com.amazonaws.services.s3.model);
if (row["urlName"] != null) {
var fullUrl = String(row["urlName"]);
var prefix = "cos:doclinks/";
var cleanFileName = fullUrl;
var fileName = "";
var fileExtension = "";
if (fullUrl != null) {
if (fullUrl.startsWith(prefix)) {
cleanFileName = fullUrl.substring(prefix.length);
}
var lastSlashIndex = cleanFileName.lastIndexOf("/");
fileName = (lastSlashIndex >= 0)
? cleanFileName.substring(lastSlashIndex + 1) : cleanFileName;
var lastDotIndex = fileName.lastIndexOf(".");
if (lastDotIndex >= 0 && lastDotIndex < fileName.length - 1) {
fileExtension = String(fileName.substring(lastDotIndex + 1).toLowerCase());
}
}
var s3Object = null; var inputStream = null;
var img = null; var bas = null;
var ios = null; var writer = null;
try {
s3Object = s3Client.getObject(new GetObjectRequest(bucketName, cleanFileName));
inputStream = s3Object.getObjectContent();
img = ImageIO.read(inputStream);
if (img == null) {
throw new Error("Failed to read image from S3: " + fileName);
}
// Convert to RGB if needed
if (img.getType() !== BufferedImage.TYPE_INT_RGB) {
var rgbImage = new BufferedImage(
img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
var g = rgbImage.createGraphics();
g.drawImage(img, 0, 0, Color.WHITE, null);
g.dispose();
img = rgbImage;
}
bas = new ByteArrayOutputStream();
var format = (["jpeg","jpg","png","bmp"].indexOf(fileExtension) >= 0)
? fileExtension : "jpeg";
if (format === "jpeg" || format === "jpg") {
var writers = ImageIO.getImageWritersByFormatName("jpeg");
if (!writers.hasNext()) throw new Error("No JPEG writer available");
writer = writers.next();
ios = ImageIO.createImageOutputStream(bas);
writer.setOutput(ios);
var param = writer.getDefaultWriteParam();
if (param.canWriteCompressed()) {
param.setCompressionMode(
javax.imageio.ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(0.3);
}
writer.write(null,
new javax.imageio.IIOImage(img, null, null), param);
} else {
if (!ImageIO.write(img, format, bas)) {
throw new Error("Failed to write image: " + format);
}
}
this.data = bas.toByteArray();
var picWidth = img.getWidth();
if (picWidth > 300) {
var aspectRatio = img.getHeight() / picWidth;
this.width = '300px';
this.height = Math.round(300 * aspectRatio) + 'px';
} else {
this.width = null; this.height = null;
}
} catch (e) {
throw new Error("Error processing '" + fileName + "': " + e.message);
} finally {
if (writer != null) writer.dispose();
if (ios != null) ios.close();
if (bas != null) bas.close();
if (inputStream != null) inputStream.close();
if (s3Object != null) s3Object.close();
}
}
Preview the report in BIRT Designer to confirm images are rendering correctly. Once verified, import the report into MAS and test it by generating a PDF against a work order or inspection that has photo attachments.

The migration from Maximo 7.6 to MAS 9 introduces a fundamental change in how attachments are stored and accessed. By shifting from a local file-based approach to S3/COS-based retrieval, the scripts powering dynamic images in BIRT reports need to be updated accordingly.
With the Initialize Script handling S3 authentication and the onCreate Script managing the object download and image processing, you can continue delivering rich, image-enhanced reports in MAS 9 just as effectively as in 7.6.
Discover everything you need to know to modernize your asset management strategy.
Inside, you’ll learn:

ActiveG, BPD Zenith, EAM Swiss, InterPro Solutions, Lexco, Peacock Engineering, Projetech, Sharptree, and ZNAPZ have united under one brand: Naviam.
You’ll be redirected to the most relevant page at Naviam.io in a few seconds — or you can
go now.