public static void main(String[] args) {
        List<Integer> manufacturerIds;
        Map<String, Integer> comObjectTableStartAddresses = new TreeMap<>();

        File contentDir = new File("../knx-webservice-content");
        if (!contentDir.exists()) {
            contentDir.mkdirs();
        }
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // Get an index of all manufacturers that have their product data online
            HttpGet getManufacturersRequest = new HttpGet("http://onlinecatalog.knx.org/Download/Manufacturers");
            try (CloseableHttpResponse response = httpClient.execute(getManufacturersRequest)) {
                HttpEntity entity = response.getEntity();

                // Copy to a local file.
                File manufacturersFile = new File(contentDir, "manufacturers.xml");
                FileUtils.copyInputStreamToFile(entity.getContent(), manufacturersFile);

                // Parse the content.
                SAXParserFactory factory = SAXParserFactory.newInstance();
                SAXParser saxParser = factory.newSAXParser();
                ManufacturerIdsHandler manufacturerIdsHandler = new ManufacturerIdsHandler();
                saxParser.parse(manufacturersFile, manufacturerIdsHandler);

                manufacturerIds = manufacturerIdsHandler.getManufacturerIds();
            } catch (SAXException | ParserConfigurationException e) {
                e.printStackTrace();
                return;
            }

            // For each manufacturer id, get the catalog / product index
            // This is a JSON file (Yeah ... I know ... why stick to one
            // format?)
            for (Integer manufacturerId : manufacturerIds) {
                File manufacturerDirectory = new File(contentDir, String.format("M-%04X", manufacturerId));
                if (!manufacturerDirectory.exists()) {
                    manufacturerDirectory.mkdirs();
                }
                boolean indexNeedsUpdate = true;
                // If the file exists, check if there's a newer version.
                File indexFile = new File(manufacturerDirectory, "catalog.json");
                JSONObject jsonObject = null;
                if (indexFile.exists()) {
                    String fileContent = FileUtils.readFileToString(indexFile, "UTF-8");
                    jsonObject = new JSONObject(fileContent);
                    int version = jsonObject.getInt("Version");
                    HttpGet checkNewerVersionRequest = new HttpGet(String.format("http://onlinecatalog.knx.org/Download/HasNewerIndexFile/%d/%d", manufacturerId, version));
                    try (CloseableHttpResponse response = httpClient.execute(checkNewerVersionRequest)) {
                        // This is an XML response just containing either:
                        // <boolean xmlns="http://schemas.microsoft.com/2003/10/Serialization/">false</boolean>
                        // or:
                        // <boolean xmlns="http://schemas.microsoft.com/2003/10/Serialization/">true</boolean>
                        if (EntityUtils.toString(response.getEntity()).contains("false")) {
                            indexNeedsUpdate = false;
                        }
                    }
                }
                if (indexNeedsUpdate) {
                    HttpGet getProductIndexRequest = new HttpGet("http://onlinecatalog.knx.org/Download/Index/" + manufacturerId);
                    try (CloseableHttpResponse response = httpClient.execute(getProductIndexRequest)) {
                        if (response.getStatusLine().getStatusCode() != 200) {
                            System.out.println("Got an unexpected status code " + response.getStatusLine().getStatusCode());
                        }
                        HttpEntity entity = response.getEntity();

                        // Save the content locally.
                        FileUtils.writeStringToFile(indexFile, EntityUtils.toString(entity).substring(3), "UTF-8");

                        String fileContent = FileUtils.readFileToString(indexFile, "UTF-8");
                        jsonObject = new JSONObject(fileContent);
                    }
                }

                // Iterate over all the products in the catalog and fetch each one.
                // Each request will result in a little ZIP file, that contains some
                // boilerplate files, but also an XML file with the product descriptor.
                JSONArray entries = jsonObject.getJSONArray("Entries");
                for (Object entry : entries) {
                    if (!(entry instanceof JSONObject)) {
                        System.out.println("Unexpected entry type");
                        continue;
                    }
                    String productId = ((JSONObject) entry).getString("Id");
                    JSONArray applicationIdentifier = ((JSONObject) entry).getJSONArray("ApplicationIdentifier");
                    int applicationId = applicationIdentifier.getInt(2) << 8 | applicationIdentifier.getInt(3);
                    int applicationVersion = applicationIdentifier.getInt(4) & 0xFF;

                    if (applicationId == 0 && applicationVersion == 0) {
                        System.out.println("SKIPPED");
                        continue;
                    }

                    String productCode = String.format("M-%04X_A-%04X-%02X", manufacturerId, applicationId, applicationVersion);

                    System.out.print("Fetching product: " + productCode + ": ");

                    // Check If we've already got that file (There are no updates, just new versions)
                    File[] files = manufacturerDirectory.listFiles((dir, name) -> name.startsWith(productCode));
                    // If we've already got the file, skip loading it
                    if (files.length > 0) {
                        System.out.println("SKIPPED");
                        continue;
                    }

                    HttpPost downloadProduct = new HttpPost("http://onlinecatalog.knx.org/Download/DownloadProduct");
                    downloadProduct.setHeader("Content-type", "application/json");
                    downloadProduct.setEntity(new StringEntity(String.format("{\"CatalogIds\":[\"%s\"],\"LanguageIds\":[\"en-US\"]}", productId)));
                    try (CloseableHttpResponse downloadProductResponse = httpClient.execute(downloadProduct)) {
                        if (downloadProductResponse.getStatusLine().getStatusCode() != 200) {
                            if (EntityUtils.toString(downloadProductResponse.getEntity()).contains("NotInAnyMarket")) {
                                // This product id is not available in the online product list
                                // Create an empty file indicating it's not in the catalog so we won't try to fetch it again.
                                File dummy = new File(manufacturerDirectory, productCode + ".failed");
                                dummy.createNewFile();
                                System.out.println("FAILED");
                            } else {
                                System.out.println("Got an unexpected status code " + downloadProductResponse.getStatusLine().getStatusCode());
                            }
                        } else {
                            String expectedPrefix = String.format("M-%04X/%s", manufacturerId, productCode);
                            try (ZipArchiveInputStream zis = new ZipArchiveInputStream(downloadProductResponse.getEntity().getContent())) {
                                ZipArchiveEntry zipFileEntry;
                                while ((zipFileEntry = zis.getNextZipEntry()) != null) {
                                    String fileName = zipFileEntry.getName();
                                    if (fileName.startsWith(expectedPrefix)) {
                                        File productFile = new File(manufacturerDirectory, fileName.substring(fileName.indexOf('/') + 1));
                                        FileUtils.copyInputStreamToFile(zis, productFile);
                                        System.out.println("SUCCESS");
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
Description of the KNX Foundation Webservice: http://onlinecatalog.knx.org/Download/help

@param args
protocols/knxnetip/src/test/java/org/apache/plc4x/protocol/knxnetip/KnxSpecificationStoreUpdater.java