> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Moving a Java application to Upsun

export const DynamicCodeBlock = ({language = 'yaml', filename, icon, lines, wrap, expandable, highlight, focus, children}) => {
  const STORAGE_KEY = 'upsun_versions_cache';
  const COMPOSABLE_STORAGE_KEY = 'upsun_composable_cache';
  const CACHE_TTL = 5 * 60 * 1000;
  const API_URL = 'https://meta.upsun.com/images';
  const COMPOSABLE_API_URL = 'https://meta.upsun.com/composable';
  const DEBUG_PREFIX = '[DynamicCodeBlock cache]';
  const [versionData, setVersionData] = useState(null);
  const [versionError, setVersionError] = useState(false);
  const [composableData, setComposableData] = useState(null);
  const [composableError, setComposableError] = useState(false);
  useEffect(() => {
    const fetchData = async () => {
      let cachedData = null;
      let cachedEtag = null;
      if (typeof localStorage !== 'undefined') {
        try {
          const cached = localStorage.getItem(STORAGE_KEY);
          if (cached) {
            const parsed = JSON.parse(cached);
            cachedData = parsed?.data || null;
            cachedEtag = parsed?.etag || null;
            if (cachedData && Date.now() - parsed.timestamp < CACHE_TTL) {
              return cachedData;
            }
          }
        } catch (err) {
          console.error('Failed to load from cache:', err);
        }
      }
      const requestHeaders = cachedEtag ? {
        'If-None-Match': cachedEtag
      } : {};
      console.debug(`${DEBUG_PREFIX} revalidating`, {
        storageKey: STORAGE_KEY,
        hasCachedData: Boolean(cachedData),
        hasCachedEtag: Boolean(cachedEtag)
      });
      const response = await fetch(API_URL, {
        headers: requestHeaders
      });
      if (response.status === 304 && cachedData) {
        console.debug(`${DEBUG_PREFIX} revalidated (304)`, {
          storageKey: STORAGE_KEY
        });
        if (typeof localStorage !== 'undefined') {
          try {
            const etag = response.headers.get('etag') || cachedEtag;
            localStorage.setItem(STORAGE_KEY, JSON.stringify({
              data: cachedData,
              etag,
              timestamp: Date.now()
            }));
          } catch (err) {
            console.error('Failed to refresh cache metadata:', err);
          }
        }
        return cachedData;
      }
      if (!response.ok) throw new Error(`API request failed: ${response.statusText}`);
      const data = await response.json();
      const etag = response.headers.get('etag');
      console.debug(`${DEBUG_PREFIX} refreshed (200)`, {
        storageKey: STORAGE_KEY,
        etag
      });
      if (typeof localStorage !== 'undefined') {
        try {
          localStorage.setItem(STORAGE_KEY, JSON.stringify({
            data,
            etag,
            timestamp: Date.now()
          }));
        } catch (err) {
          console.error('Failed to cache data:', err);
        }
      }
      return data;
    };
    fetchData().then(data => setVersionData(data)).catch(err => console.error('Failed to fetch version data:', err));
  }, []);
  const findHighestVersion = versionsMap => {
    if (!versionsMap || Object.keys(versionsMap).length === 0) return null;
    const entries = Object.entries(versionsMap);
    const active = entries.filter(([, v]) => v.upsun && v.upsun.status === 'supported' || v.upsun && v.upsun.status === 'deprecated');
    const candidates = active.length > 0 ? active : entries;
    let [highestName] = candidates[0];
    for (let i = 1; i < candidates.length; i++) {
      const [currentName] = candidates[i];
      const cp = currentName.split('.').map(Number);
      const hp = highestName.split('.').map(Number);
      for (let j = 0; j < Math.max(cp.length, hp.length); j++) {
        if ((cp[j] || 0) > (hp[j] || 0)) {
          highestName = currentName;
          break;
        } else if ((cp[j] || 0) < (hp[j] || 0)) {
          break;
        }
      }
    }
    return highestName;
  };
  const getVersion = (lang, requestedVersion = 'latest') => {
    if (lang === 'composable') {
      if (!composableData || !composableData.versions || Object.keys(composableData.versions).length === 0) return null;
      if (requestedVersion && requestedVersion !== 'latest') {
        return (requestedVersion in composableData.versions) ? requestedVersion : null;
      }
      return findHighestVersion(composableData.versions);
    }
    if (!versionData) return null;
    const imageData = versionData[lang];
    if (!imageData || !imageData.versions || Object.keys(imageData.versions).length === 0) {
      return null;
    }
    if (requestedVersion && requestedVersion !== 'latest') {
      return (requestedVersion in imageData.versions) ? requestedVersion : null;
    }
    return findHighestVersion(imageData.versions);
  };
  let code = typeof children === 'string' ? children : String(children || '');
  const codeLines = code.split('\n');
  while (codeLines.length > 0 && codeLines[0].trim() === '') codeLines.shift();
  while (codeLines.length > 0 && codeLines[codeLines.length - 1].trim() === '') codeLines.pop();
  if (codeLines.length > 0) {
    const indents = codeLines.filter(line => line.trim().length > 0).map(line => line.match(/^[ \t]*/)[0].length);
    const minIndent = Math.min(...indents);
    code = codeLines.map(line => line.slice(minIndent)).join('\n');
  }
  code = code.replace(/\{\{version:(.*?)\}\}/g, (match, params) => {
    const parts = params.split(':');
    const lang = parts[0];
    const ver = parts[1] || 'latest';
    const isComposable = lang === 'composable';
    const hasError = isComposable ? composableError : versionError;
    const dataReady = isComposable ? composableData !== null : versionData !== null;
    if (hasError) return '(unavailable)';
    if (dataReady) {
      const resolvedVersion = getVersion(lang, ver);
      return resolvedVersion || match;
    }
    return '...';
  });
  const codeBlockProps = {
    language,
    ...filename && ({
      filename
    }),
    ...icon && ({
      icon
    }),
    ...lines !== undefined && ({
      lines
    }),
    ...wrap !== undefined && ({
      wrap
    }),
    ...expandable !== undefined && ({
      expandable
    }),
    ...highlight && ({
      highlight
    }),
    ...focus && ({
      focus
    })
  };
  return <CodeBlock {...codeBlockProps}>{code}</CodeBlock>;
};

export const MetaImageVersion = ({language, version}) => {
  const [selectedVersion, setSelectedVersion] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const isComposable = language === 'composable';
  const STORAGE_KEY = isComposable ? 'upsun_composable_cache' : 'upsun_versions_cache';
  const CACHE_TTL = 5 * 60 * 1000;
  const API_URL = isComposable ? 'https://meta.upsun.com/composable' : 'https://meta.upsun.com/images';
  const findHighestVersion = versionsMap => {
    if (!versionsMap || Object.keys(versionsMap).length === 0) return null;
    const entries = Object.entries(versionsMap);
    const active = entries.filter(([, v]) => v.upsun && v.upsun.status === 'supported' || v.upsun && v.upsun.status === 'deprecated');
    const candidates = active.length > 0 ? active : entries;
    let [highestName] = candidates[0];
    for (let i = 1; i < candidates.length; i++) {
      const [currentName] = candidates[i];
      const cp = currentName.split('.').map(Number);
      const hp = highestName.split('.').map(Number);
      for (let j = 0; j < Math.max(cp.length, hp.length); j++) {
        if ((cp[j] || 0) > (hp[j] || 0)) {
          highestName = currentName;
          break;
        } else if ((cp[j] || 0) < (hp[j] || 0)) {
          break;
        }
      }
    }
    return highestName;
  };
  useEffect(() => {
    if (!language) {
      setLoading(false);
      return;
    }
    setLoading(true);
    setError(null);
    const fetchData = async () => {
      let cachedData = null;
      let cachedEtag = null;
      if (typeof localStorage !== 'undefined') {
        try {
          const cached = localStorage.getItem(STORAGE_KEY);
          if (cached) {
            const parsed = JSON.parse(cached);
            cachedData = parsed?.data || null;
            cachedEtag = parsed?.etag || null;
            if (cachedData && Date.now() - parsed.timestamp < CACHE_TTL) return cachedData;
          }
        } catch (error_) {
          console.error('Failed to load from cache:', error_);
        }
      }
      const requestHeaders = cachedEtag ? {
        'If-None-Match': cachedEtag
      } : {};
      const response = await fetch(API_URL, {
        headers: requestHeaders
      });
      if (response.status === 304 && cachedData) {
        if (typeof localStorage !== 'undefined') {
          try {
            const etag = response.headers.get('etag') || cachedEtag;
            localStorage.setItem(STORAGE_KEY, JSON.stringify({
              data: cachedData,
              etag,
              timestamp: Date.now()
            }));
          } catch (error_) {
            console.error('Failed to refresh cache metadata:', error_);
          }
        }
        return cachedData;
      }
      if (!response.ok) throw new Error(`API request failed: ${response.statusText}`);
      const data = await response.json();
      const etag = response.headers.get('etag');
      if (typeof localStorage !== 'undefined') {
        try {
          localStorage.setItem(STORAGE_KEY, JSON.stringify({
            data,
            etag,
            timestamp: Date.now()
          }));
        } catch (error_) {
          console.error('Failed to cache data:', error_);
        }
      }
      return data;
    };
    fetchData().then(data => {
      if (!data) {
        setSelectedVersion(null);
        setLoading(false);
        return;
      }
      const imageData = isComposable ? data : data[language];
      if (!imageData || !imageData.versions || Object.keys(imageData.versions).length === 0) {
        setSelectedVersion(null);
        setLoading(false);
        return;
      }
      let versionName = null;
      if (version && version !== 'latest') {
        versionName = (version in imageData.versions) ? version : null;
      } else {
        versionName = findHighestVersion(imageData.versions);
      }
      setSelectedVersion(versionName);
      setLoading(false);
    }).catch(error_ => {
      console.error('MetaImageVersion error:', error_);
      setError(error_.message);
      setLoading(false);
    });
  }, [language, version]);
  if (loading) return <span>…</span>;
  if (error) return <span title={error}>⚠ unavailable</span>;
  if (!selectedVersion) return <span>No version found</span>;
  return <span>{selectedVersion}</span>;
};

export const DisclaimerNix = () => <Tip>
    You can now use composable image to install runtimes and tools in your application container. To find out more, see the <a href="/docs/configure-apps/app-reference/composable-image">Composable image</a> topic.
  </Tip>;

<DisclaimerNix />

It is common to have a Java application that you want to migrate to Upsun.
Upsun supports several styles of Java application, such as monolith, microservices, stateful, and stateless.

## Minimum Requirement

To run a Java application at Upsun you need:

* [A supported Java version](/docs/languages/java#supported-versions)
* [A build management tool](/docs/languages/java#support-build-automation)
  * [Gradle](https://docs.gradle.org/current/userguide/gradle_wrapper.html)
  * [Maven](https://maven.apache.org/)
  * [Maven Wrapper](https://www.baeldung.com/maven-wrapper)
  * [Ant](https://ant.apache.org/)
* A Git Repository:
  * [GitHub](/docs/integrations/source/github)
  * [BitBucket](/docs/integrations/source/bitbucket)
  * [GitLab](/docs/integrations/source/gitlab)
  * The default Git repository provided by Upsun

<Note>
  A container application can't be bigger than **8 GB** of memory.
  For more details, see [tuning](/docs/languages/java/tuning).
</Note>

## Monolith/Single Application

To start a Java application, you need to understand the [Upsun structure](/docs/core-concepts/structure).
You will need to configure your [application](/docs/configure-apps), [routes](/docs/routes),
and [services](/docs/add-services).

### Application

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        myapp:
          type: 'java:<VERSION>'

          hooks:
            build: [2]
          web:
            commands:
              start: [3]
    `
  }
</DynamicCodeBlock>

1. [A Java version](/docs/languages/java#supported-versions), e,g.: `java:<VERSION>`.
2. [Hooks define what happens when building the application](/docs/configure-apps/hooks). This build process typically generates an executable file such as a uber-jar. For example, `mvn clean package`.
3. [The commands key defines the command to launch the application](/docs/configure-apps/image-properties/web#web-commands). For example,  `java -jar file.jar`.
4. In the start's command needs to receive the port where the application will execute thought the `PORT` environment. That's best when your app follows the port bind principle. For example, `java -jar jar --port=$PORT`.

<Note>
  Be aware that after the build, it creates a read-only system. You have the [mount option to create a writable folder](/docs/configure-apps/image-properties/mounts).
</Note>

### Route

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      routes:
        "https://{default}/":
          type: upstream
          upstream: "myapp:http" [1]
        "https://www.{default}/":
          type: redirect
          to: "https://{default}/"

      applications:
        myapp:
          type: 'java:<VERSION>'

          hooks:
            build: [2]
          web:
            commands:
              start: [3]
    `
  }
</DynamicCodeBlock>

1. It defines the application will link in the route. For example,`"myapp:http"`.

<Note>
  Application instances have a limited amount of memory at build time, which has a maximum of 8 GB.
  At runtime that limit depends on your plan and configuration.
  A stateless application can be scaled horizontally to multiple application instances using Varnish in a [load balancer](https://support.platform.sh/hc/en-us/community/posts/16439676899474) configuration.
</Note>

<Note>
  Application instances have a limited amount of memory at build time, which has a maximum of 8 GB.
  At runtime that limit depends on [the resources you have defined for your application container](/docs/manage-resources) using `upsun resources:set`.
  A stateless application can be scaled horizontally to multiple application instances with `upsun resources:set` or by using Varnish in a [load balancer](https://support.platform.sh/hc/en-us/community/posts/16439676899474) configuration.
</Note>

## Microservices

You have the option to use several languages in microservices. If you're using Java there are several options to aggregate these services into a microservices:

* [Maven Modules](https://maven.apache.org/guides/mini/guide-multiple-modules.html)
* [Gradle Multi-project](https://guides.gradle.org/creating-multi-project-builds/)
* [Git submodules](/docs/development/submodules)

[Upsun supports multiple applications](/docs/configure-apps/multi-app) and there are two options:

* One application YAML file to each application
* Aggregate all applications in a single file with an `.upsun/config.yaml` file

| Article                                                                                                                       | Content                                                                        |
| ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| [Microservices in the cloud, part two](https://devcenter.upsun.com/posts/microservices-in-the-cloud-part-two/)                | [Source](https://github.com/EventosJEspanol/latin-america-micro-profile)       |
| [Microservices in the cloud, part one](https://upsun.com/blog/)                                                               | [Source](https://github.com/EventosJEspanol/latin-america-micro-profile)       |
| [Multiple Applications](https://support.platform.sh/hc/en-us/community/posts/16439649733010)                                  | [Source](https://github.com/platformsh-examples/tomcat-multi-app)              |
| [Configure multi-applications with `.upsun/config.yaml`](https://support.platform.sh/hc/en-us/community/posts/16439676928274) | [Source](https://github.com/platformsh-examples/tomcat-multi-app-applications) |

<Note>
  You can load balance to some or [all applications in the project cluster](https://support.platform.sh/hc/en-us/community/posts/16439662235026).

  While the table above shows examples for Upsun Fixed rather than for Upsun Flex, the same rules apply with only slight changes in configuration.
</Note>

## Access to managed services

Upsun provides [managed services](/docs/add-services) such as databases, cache, and search engines.
However, you can use an external database or other services during a transition process. To maintain security, you must ensure that your external service (e.g., your external database or a third-party API)
is configured to accept connections from Upsun’s outbound IP addresses.

When applications need to access a *managed* service, it is important to include the [`relationships` key](/docs/configure-apps/image-properties/relationships).
By default an application may not talk to any other container without a `relationship` explicitly allowing access.

The most common mechanisms for connecting to a managed service are listed below.

### Overwrite

If you are using a framework that follows the [Twelve-Factor App](https://12factor.net/) methodology, particularly the [third point](https://12factor.net/config), you can configure the application directly from environment variables.
Examples of such frameworks include Spring, Eclipse MicroProfile Config, Quarkus, and Micronauts.

Service credentials are available within the [service environment variables](/docs/development/variables#service-environment-variables), or the [`PLATFORM_RELATIONSHIPS` environment variable](/docs/development/variables/use-variables#use-provided-variables).

<Tabs>
  <Tab title="Service environment variables">
    Assuming the relationship `postgresql` is configured to grant access to a PostgreSQL service container, you can map the automatically generated environment variable (`POSTGRESQL_HOST`) to whatever your application expects to use:

    ```bash .environment theme={null}
    export DB_HOST="$POSTGRESQL_HOST"
    ```

    This sets environment variables with the names your app needs,
    and the values from [service environment variables](/docs/development/variables#service-environment-variables).
  </Tab>

  <Tab title="`PLATFORM_RELATIONSHIPS` environment variable">
    This variable is a base64-encoded JSON object with keys of the relationship name and values of arrays of relationship endpoint definitions.

    Upsun supports the [`jq` tool](https://stedolan.github.io/jq/), which allows to extract information from this JSON.

    ```bash .environment theme={null}
    export DB_HOST="$(echo "$PLATFORM_RELATIONSHIPS" | base64 --decode | jq -r '.postgresql[0].host')"
    ```

    This sets environment variables with names your app needs and the values from [`PLATFORM_RELATIONSHIPS` environment variable](/docs/development/variables/use-variables#use-provided-variables).
  </Tab>
</Tabs>

| Article                                                                                               | Source                                                                                                   |
| ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| [Spring Data MongoDB](https://support.platform.sh/hc/en-us/community/posts/16439654854802)            | [Source](https://github.com/platformsh-examples/java-overwrite-configuration/tree/master/spring-mongodb) |
| [Jakarta EE/MicroProfile Config](https://support.platform.sh/hc/en-us/community/posts/16439700735122) | [Source](https://github.com/platformsh-examples/java-overwrite-configuration/tree/master/jakarta-nosql)  |
| [Spring Data JPA](https://support.platform.sh/hc/en-us/community/posts/16439669562130)                | [Source](https://github.com/platformsh-examples/java-overwrite-configuration/tree/master/spring-jpa)     |
| [Payara JPA](https://support.platform.sh/hc/en-us/community/posts/16439658290194)                     | [Source](https://github.com/platformsh-examples/java-overwrite-configuration/blob/master/payara/README)  |

<Note>
  While the table above shows examples for Upsun Fixed rather than for Upsun Flex, the same rules apply with only slight changes in configuration.
</Note>

To reduce the number of lines in the application file and to make it cleaner,
you have the option to move the variable environment to another file: a [`.environment` file](/docs/development/variables/set-variables#set-variables-via-script).

**Example:**

You can obtain relationship information through the [service environment variables](/docs/development/variables#service-environment-variables) themselves,
or through the [`PLATFORM_RELATIONSHIPS` environment variable](/docs/development/variables/use-variables#use-provided-variables).
Say your application has a relationship named `postgresql` to a database service named `postgresql`:

<Tabs>
  <Tab title="Service environment variables">
    ```bash .environment theme={null}
    export DB_HOST="${POSTGRESQL_HOST}"
    export DB_PASSWORD="${POSTGRESQL_PASSWORD}"
    export DB_USER="${POSTGRESQL_USERNAME}"
    export DB_DATABASE="${POSTGRESQL_PATH}"
    export JDBC="jdbc:postgresql://${HOST}/${DATABASE}"
    export JAVA_MEMORY="-Xmx$(jq .info.limits.memory /run/config.json)m"
    export JAVA_OPTS="$JAVA_MEMORY -XX:+ExitOnOutOfMemoryError"
    ```

    This sets environment variables with the names your app needs,
    and the values from [service environment variables](/docs/development/variables#service-environment-variables).
  </Tab>

  <Tab title="`PLATFORM_RELATIONSHIPS` environment variable">
    This `PLATFORM_RELATIONSHIPS` variable is a base64-encoded JSON object with keys of the relationship name and values of arrays of relationship endpoint definitions.

    Upsun supports the [`jq` tool](https://stedolan.github.io/jq/), which allows to extract information from this JSON.

    ```bash .environment theme={null}
    export DB_HOST="$(echo "$PLATFORM_RELATIONSHIPS" | base64 --decode | jq -r ".postgresql[0].host')"
    export DB_PASSWORD="$(echo "$PLATFORM_RELATIONSHIPS" | base64 --decode | jq -r ".postgresql[0].password')"
    export DB_USER="$(echo "$PLATFORM_RELATIONSHIPS" | base64 --decode | jq -r ".postgresql[0].username')"
    export DB_DATABASE="$(echo "$PLATFORM_RELATIONSHIPS" | base64 --decode | jq -r ".postgresql[0].path')"
    export JDBC="jdbc:postgresql://${HOST}/${DATABASE}"
    export JAVA_MEMORY="-Xmx$(jq .info.limits.memory /run/config.json)m"
    export JAVA_OPTS="$JAVA_MEMORY -XX:+ExitOnOutOfMemoryError"
    ```

    This sets environment variables with names your app needs and the values from [`PLATFORM_RELATIONSHIPS` environment variable](/docs/development/variables/use-variables#use-provided-variables).
  </Tab>
</Tabs>

This `.environment` file can interact to each application file.

**Example:**

<DynamicCodeBlock language="yaml" filename=".upsun/config.yaml">
  {`
      applications:
        # The app's name, which must be unique within the project.
        myapp:
          type: 'java:{{version:java:latest}}'
          hooks:
            build: ./mvnw package -DskipTests -Dquarkus.package.uber-jar=true
          relationships:
            postgresql:
          web:
            commands:
              start: java -jar $JAVA_OPTS $CREDENTIAL -Dquarkus.http.port=$PORT jarfile.jar`
  }
</DynamicCodeBlock>
