Milesoft LinkedIn Extension (linkedin-ext)
1. Overview & Purpose
The LinkedIn Extension (linkedin-ext) is an enterprise-grade facade library designed for the Milesoft ecosystem. It simplifies and standardizes interaction with the LinkedIn REST API, allowing downstream applications to publish rich content, post text updates, and perform multi-step media uploads.
By wrapping low-level LinkedIn Rest.li endpoints, linkedin-ext ensures that compliance, header standards, and security are maintained consistently across all applications and microservices.
Key Capabilities & Core Benefits:
- Automatic Organization URN Scoping: Encapsulates the target organization (company ID) and automatically scopes API interactions (e.g., converting
"66763891"to"urn:li:organization:66763891"). - Multi-Source Image Processing: Seamlessly uploads images regardless of how they are represented. Supports local files (
java.nio.file.Path), memory-backed assets (java.awt.image.BufferedImage), or remote web-accessible URLs (e.g., Cloudinary-hosted resources). - Standardized Rest.li Header Injection: Injects mandatory, modern headers into all requests to ensure strict version pinning and compatibility:
X-Restli-Protocol-Version: 2.0.0LinkedIn-Version: 202511
- Output ID Resolution: Automatically extracts the response header
x-restli-id(the newly generated Post/Share URN) on successful publication, returning a clean, traceable reference identifier. - Robust Exception Model: Ensures strict validation of API request payloads utilizing static preconditions prior to dispatching network calls.
2. Installation & Dependency Configuration
The linkedin-ext extension package is hosted inside our secure Google Cloud Artifact Registry.
To pull this package into your downstream JVM microservice or application, configure your build settings as follows:
2.1 Gradle Setup (build.gradle)
Register our secure Artifact Registry URL and include the dependency:
plugins {
id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.5"
id "java"
}
repositories {
mavenLocal()
mavenCentral()
maven {
url "artifactregistry://us-west2-maven.pkg.dev/milesoft-repo/repo-maven"
}
}
dependencies {
// Required Milesoft base dependencies
implementation "io.milesoft:commons:2.0.1"
implementation "io.milesoft:clients:2.0.1"
// Milesoft LinkedIn Extension
implementation "io.milesoft:linkedin-ext:2.0.1"
}
3. Core Entities & Domain Models
The library enforces type-safety and minimizes developer error via cohesive domain representations.
3.1 Image
The Image class represents a rich media upload payload. It features a Lombok-backed builder and handles all standard image sources:
import java.awt.image.BufferedImage;
import java.nio.file.Path;
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;
@Getter
@Builder(builderClassName = "Builder")
public class Image {
@NonNull
private final String title;
@NonNull
private final String fileName;
private final Path file;
private final BufferedImage data;
private final String url;
}
3.2 LinkedIn REST Protocol Constants
Standardized Rest.li protocol definitions are defined under io.milesoft.linkedin.constants.LinkedInConstants:
| Constant Key | Value | Description |
|---|---|---|
VISIBILITY_PUBLIC |
"PUBLIC" |
Sets visibility context to public feed |
DISTRIBUTION_MAIN_FEED |
"MAIN_FEED" |
Standard feed delivery channel |
LIFECYCLE_STATE_PUBLISHED |
"PUBLISHED" |
Direct publishing state |
PREFIX_ORGANIZATION |
"urn:li:organization:" |
Core author organization prefix |
PREFIX_IMAGE |
"urn:li:image:" |
Core uploaded image prefix |
HEADER_PROTOCOL_KEY |
"X-Restli-Protocol-Version" |
Rest.li Protocol Key |
HEADER_PROTOCOL_VALUE |
"2.0.0" |
Protocol version standard |
HEADER_VERSION_KEY |
"LinkedIn-Version" |
LinkedIn Version Pin Key |
HEADER_VERSION_VALUE |
"202511" |
API Version Target |
HEADER_POST_ID |
"x-restli-id" |
Post identifier returned in response |
4. Spring Boot Integration & Bean Configuration
Integrating the LinkedIn Extension into a Spring Boot service is clean and declarative.
4.1 Define the Config Class
Instantiate the service class as a bean, specifying the company organization ID retrieved from application properties:
import io.milesoft.linkedin.services.LinkedInService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SocialConfig {
@Bean
public LinkedInService linkedInService(@Value("${linkedIn.companyId}") String companyId) {
return new LinkedInService(companyId);
}
}
4.2 Property Definition (application.yml)
Configure the respective target variables under your profile configs:
linkedIn:
companyId: "12345678"
5. API & Usage Guide
Downstream clients can interact with LinkedInService via simple, high-level methods.
5.1 Share a Text-Only Post
Publish a lightweight text-only message to the company's main feed:
import io.milesoft.linkedin.services.LinkedInService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PublishingService {
@Autowired
private LinkedInService linkedInService;
public void publishUpdate(String content, String oauthToken) {
// Publishes the post and returns the unique LinkedIn URN (e.g. "urn:li:share:7123456")
String postUrn = linkedInService.shareText(content, oauthToken);
System.out.println("Text update published: " + postUrn);
}
}
5.2 Share a Post with a Media Asset (Image)
Upload and publish a rich image post to the company's main feed:
import io.milesoft.linkedin.domain.Image;
import io.milesoft.linkedin.services.LinkedInService;
import java.nio.file.Paths;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class SocialMarketingService {
@Autowired
private LinkedInService linkedInService;
public void publishCampaign(String message, String imageUrl, String oauthToken) {
// Build image model targeting a remote URL (downloaded & processed automatically)
Image image = Image.builder()
.title("Launch Campaign 2026")
.fileName("campaign_launch.png")
.url(imageUrl)
.build();
// Performs upload and posts to the main feed under organizational authorship
String postUrn = linkedInService.shareImage(image, message, oauthToken);
System.out.println("Media post successfully published! URN: " + postUrn);
}
}
6. Under-the-Hood Mechanics & Network Flow
For complex image sharing operations, the extension encapsulates a multi-step sequence to satisfy LinkedIn's strict secure content delivery guidelines:
[Downstream Client] [LinkedInService] [LinkedIn REST API]
| | |
|----- shareImage(image, msg, tok) ---->| |
| |----- 1. Initialize Upload -------->|
| |<---- returns binary PUT URL -------|
| | |
| |----- 2. PUT binary payload ------->| (Amazon S3 direct storage)
| |<---- 201 Created ------------------|
| | |
| |----- 3. POST Share Payload ------->| (Contains media URN + post msg)
| |<---- returns Header: x-restli-id --|
|<---- Returns unique Post URN ---------| |
Flow Breakdown:
- Initialize Upload: Dispatches a
POSTrequest tohttps://api.linkedin.com/rest/images?action=initializeUploadspecifying the owner organization. It returns a target AWS S3 pre-signed upload URL along with a registered image asset URN (urn:li:image:<imageId>). - Binary Push: Streams the raw binary image payload using a
PUTrequest with custom Multi-part / File handlers directly to the pre-signed storage URL. - Post Publication: Dispatches a final
POSTtohttps://api.linkedin.com/rest/postsspecifying the commentary text, public feed visibility, and ties the newly uploaded media URN to the post content. - Header Interception: The response parses the response header list, retrieves the
x-restli-idvalue containing the unique post identifier, and returns it to the client.
7. Testing & Verification
For local development and verification patterns, integration tests are written under src/test/java utilizing custom git-ignored configuration property files.
7.1 Integration Test Example
Place private test credentials under /src/test/resources/application.private.properties (never commit this file) and execute tests:
import io.milesoft.commons.utils.PropertyUtils;
import io.milesoft.linkedin.domain.Image;
import java.nio.file.Path;
import java.util.Properties;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
public class TestLinkedInService {
private LinkedInService linkedInService;
private String accessToken;
@Before
public void before() {
// Loads local, developer-only properties
final Properties properties = PropertyUtils.read("/application.private.properties");
final String companyId = properties.getProperty("linkedIn.companyId");
this.accessToken = properties.getProperty("linkedIn.accessToken");
this.linkedInService = new LinkedInService(companyId);
}
@Test
@Ignore("Integration test - run locally with private credentials only")
public void testShareText() {
String message = "Direct integration test post from Milesoft SDK";
String urnId = linkedInService.shareText(message, accessToken);
System.out.println("Verified text share: " + urnId);
}
}